Developers
Build bots for Hushpiper.
A small API for bots that join chats and answer: a quiz for a book club, order updates from a shop, a team's morning stand-up. A bot reads only what people send to it, and everyone in the chat can see it's there.
At a glance
A bot, in four parts
A bot is an account your code runs. People add it to a chat or message it directly, and it answers.
Create
Make a bot in the developer console. It gets a name, a picture and a username ending in _bot.
Receive
Messages sent to your bot reach your server as signed webhooks, or ask for them with GET /updates.
Reply
One call sends text, buttons or a photo back to the chat. Editing and deleting work the same way.
Stay private
Messages to a bot are sealed for that bot alone. Everything else in the chat stays end-to-end encrypted between people.
Getting started
Three steps to a first reply
Keys are handed out by a person, not a sign-up form.
Ask for a key
Tell us what your bot will do and who it's for (see Onboarding). We set up your account with you.
Create a bot
In the console you get an API key (hp_live_…) and the bot's own encryption key, which stays on your server.
Listen and reply
Set your webhook address, then answer each message with one call. Our bot library (Node.js and Python first) seals and opens messages for you.
# Every call sends your key. Who am I?
curl https://hushpiper.com/api/bot/v1/me \
-H "Authorization: Bearer hp_live_…"
# 200 OK
{ "id": "b_7Hq2", "username": "bookclub_bot", "name": "Book Club Quiz", "state": "live" }
Endpoints
Small on purpose
Everything a bot does, in ten routes. There is no route that reads a chat's history or lists people who didn't talk to the bot.
| Method | Route | What it does |
|---|---|---|
| GET | /me | The bot's name, username, picture and state. |
| PATCH | /me | Change its name, picture, description and the commands it lists. |
| PUT | /webhook | Where updates are sent, and the secret they're signed with. |
| GET | /updates?after=… | New messages and events, for a bot that asks instead of taking webhooks. |
| GET | /chats/{chat} | The chat's name and what its admins let the bot do there. |
| GET | /chats/{chat}/keys | The public keys a reply is sealed with. No names, nothing else. |
| POST | /chats/{chat}/messages | Send a message: text, buttons, a photo or a file. |
| PATCH | /chats/{chat}/messages/{id} | Edit a message the bot sent. |
| DELETE | /chats/{chat}/messages/{id} | Delete a message the bot sent. |
| POST | /chats/{chat}/leave | Leave a chat. |
| HOOK | message, button, added, removed | What your webhook receives. |
Every route starts https://hushpiper.com/api/bot/v1. Answers are JSON; times are UTC, like 2026-09-26T14:02:11Z.
The flow
Message in, reply out
Someone writes to the bot, your server hears about it, the bot answers. Our server passes on sealed envelopes and never sees the words.
1. Someone writes to the bot
In a direct chat, or in a group with a command like /quiz, a mention of the bot, or a reply to it. Your webhook gets:
POST https://your-server.example/hushpiper
X-Hushpiper-Signature: t=1790431200,v1=5f2c9e…
{
"update_id": 1042,
"type": "message",
"chat": { "id": "c_8Fq2", "type": "group", "title": "Book club" },
"from": { "username": "maya" },
"message": { "id": "m_91kD", "sealed": "hp1.AAAB…" }
}
2. Your bot opens it
Only the bot's own key opens sealed. The library checks the signature and opens it in one line:
const msg = await bot.open(request); // { text: "/quiz", from: "maya", chat: "c_8Fq2" }
3. The bot answers
The library seals the reply for the people in the chat and sends it. Send the same Idempotency-Key again and nothing is sent twice.
await bot.reply(msg, 'Question 1: who wrote Middlemarch?', {
buttons: [['George Eliot', 'Jane Austen'], ['Charlotte Brontë']],
});
# What it sends
POST /api/bot/v1/chats/c_8Fq2/messages
Idempotency-Key: 6f1d2c9a-41be-4c1e-9f0a-2d7c55e0b3a1
{ "sealed": "hp1.AAAC…", "reply_to": "m_91kD" }
# 201 Created
{ "id": "m_91kE", "sent_at": "2026-09-26T14:02:11Z" }
Bot states
Where a bot is in its life
The same states you see in the console, and in GET /me.
draft- Just made. Only you can talk to it, in a direct chat.
in_review- You asked for it to be listed where people can find it. A person is checking it against our guidelines.
live- People can find it, message it and add it to their chats.
paused- You paused it. It stays in its chats, says it's paused, and gets no messages.
suspended- We stopped it, usually after reports. You're told why, and you can appeal.
Privacy
What a bot can and can't see
Hushpiper stays end-to-end encrypted for people. A bot is let in only to the messages meant for it.
A bot can see
- Messages sent to it: everything in a direct chat with it; in a group, only commands (
/quiz), mentions of it and replies to it. - The username of whoever wrote to it.
- The chat's name, and what the chat's admins let it do.
A bot can't see
- Any other message in the chat. Those stay sealed between people's devices.
- Phone numbers, email addresses, online status, last seen or read receipts.
- Who else is in the chat, unless the chat shows its members to everyone.
- Anyone who hasn't messaged it or added it: a bot can't start a chat.
That line shows in the chat when a bot joins, and on the bot's profile. Group admins decide which bots join, and can remove one at any time.
Errors, limits
What a refusal looks like
Every error has a stable code for your program and a plain message for your logs.
| Status | Code | What it means |
|---|---|---|
| 400 | bad_request | Not valid JSON, or a field is missing. |
| 401 | unauthorized | No key, or a key that was revoked. |
| 403 | not_allowed_here | The bot isn't in that chat, or its admins don't allow that. |
| 404 | not_found | No such chat or message, or not one the bot can see. |
| 409 | idempotency_conflict | That Idempotency-Key was used with a different request. |
| 422 | invalid | A field is wrong. The message says which. |
| 429 | rate_limited | Too fast. Retry-After says when to try again. |
# 403 Forbidden
{
"error": {
"code": "not_allowed_here",
"message": "The admins of this chat don't let bots send photos."
}
}
Limits
Up to 30 messages a second across all chats, and 20 a minute in any one group. Over that, you get a 429.
Idempotency
Send an Idempotency-Key with every POST. The same key within 24 hours returns the first answer instead of sending twice.
Signed webhooks
X-Hushpiper-Signature is an HMAC-SHA256 of the time and the body, with your webhook secret. Refuse anything older than 5 minutes. Failed deliveries are retried for 24 hours.
Onboarding
Talk to us for a key
The Bot API opens to a few partners first. Write to us with:
- what your bot will do, and who it's for;
- roughly how many chats you expect;
- what you need first: webhooks, buttons, files or payments.
The console signs in with your Hushpiper account.