Authentication
Each integration has its own credential. Bot API calls send the bot’s token, incoming webhooks carry a secret token in their URL, and event webhook deliveries are signed so you can check that they come from DonutChat.
| Credential | Used for | Looks like | Where you get it |
|---|---|---|---|
| Bot token | Bot HTTP API and event stream | donutbot_… | Shown once when you create an API bot |
| Webhook URL | Incoming webhooks | https://donutchat.com/webhooks/v1/{webhook_id}/{token} | Shown once when you create the webhook |
| Signing secret | Checking event webhook deliveries | whsec_… | Shown once when you set up the event webhook |
The app doesn’t show any of these again. If you lose one, replace it: regenerate the token, rotate the webhook URL or regenerate the secret.
Bot tokens
Section titled “Bot tokens”Send the token in the Authorization header of every bot API request:
Authorization: Bearer donutbot_YOUR_TOKEN_HERE- Write
Bearerwith this exact capitalization, then one space and the whole token, includingdonutbot_. - One token works in every chat the bot belongs to. Anyone who has it can post as the bot there and receive what people send it.
curl https://api.donutchat.com/bots/v1/chats \ -H "Authorization: Bearer donutbot_YOUR_TOKEN_HERE"If the token isn’t accepted, the request fails with 401. Retrying won’t help; fix the token first.
| HTTP | error | message | Cause |
|---|---|---|---|
| 401 | unauthorized | Missing or invalid Authorization header. Use: Authorization: Bearer <token> | The header is missing or doesn’t start with Bearer and a space. |
| 401 | unauthorized | Invalid bot token | The token is wrong or was regenerated, or the bot is paused. |
The file download endpoints use the message Bot bearer token required when the header is missing or doesn’t start with Bearer and a space.
Event stream
Section titled “Event stream”The event stream takes the same header. If your WebSocket client can’t set headers, as with WebSocket APIs that follow the browser standard, put the token in the token query parameter instead:
wss://api.donutchat.com/bots/v1/stream?token=donutbot_YOUR_TOKEN_HEREDonutChat uses the query parameter only when the request has no Authorization: Bearer … header. If a request has both, DonutChat uses the header, even when its token is wrong. Only the event stream accepts the query parameter. Use the header when your client supports it, because URLs are more likely than headers to end up in logs.
Regenerate a bot token
Section titled “Regenerate a bot token”Regenerate the token if you lose it or it may have leaked. Open the bot in My Bots, open its menu, select Regenerate Token, then Regenerate. You can do this on iPhone, Android and the web app, but not in the desktop app. See Regenerate the API token.
Requests with the old token fail with 401 right away. Update your integration with the new token and restart it.
Pausing the bot also makes every request fail with 401 Invalid bot token until you switch Active back on.
Webhook URLs
Section titled “Webhook URLs”An incoming webhook has no separate key: the token is the last part of its URL, so the whole URL is the credential. Don’t add an Authorization header; it’s ignored.
- Anyone who has the URL can post in the chat. Keep it out of source code, logs and chats.
- If the token is wrong, was rotated or belongs to a deleted webhook, the request fails with 401
unauthorizedand the messageInvalid webhook token. - To replace the URL, open the webhook in the app, select Rotate Token, then Rotate. The old URL stops working immediately. Copy the new one from New Bot URL; it’s shown only once.
- To stop a webhook without changing its URL, switch it to Inactive. Requests then fail with 403
webhook_disabled.
See Incoming webhooks for the steps in the app.
Event webhook signatures
Section titled “Event webhook signatures”When your bot has an event webhook, DonutChat signs every delivery with the bot’s signing secret. On iPhone, Android and the web app, the secret appears once, in Webhook Signing Secret, when you first set the webhook URL and each time you select Regenerate Secret.
Every delivery has this header:
X-Donut-Signature: sha256=…The value is sha256= followed by the HMAC-SHA256 of the raw request body as lowercase hex. The key is the whole signing secret, including whsec_.
To check a delivery:
- Read the request body as raw bytes, before any JSON parsing.
- Compute the HMAC-SHA256 of those bytes with your signing secret as the key, and hex-encode it in lowercase.
- Put
sha256=in front and compare the result with theX-Donut-Signatureheader, using a constant-time comparison. - If they don’t match, reject the request with 401 and don’t process it.
import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody is a Buffer with the request body exactly as received.function isValidSignature(rawBody, signatureHeader, secret) { const expected = Buffer.from('sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex')); const received = Buffer.from(signatureHeader ?? ''); return received.length === expected.length && timingSafeEqual(received, expected);}
// isValidSignature(rawBody, req.headers['x-donut-signature'], process.env.DONUT_WEBHOOK_SECRET)import hashlibimport hmac
def is_valid_signature(raw_body, signature_header, secret): """raw_body is the request body exactly as received, as bytes.""" expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected.encode(), (signature_header or "").encode())
# is_valid_signature(raw_body, headers.get("X-Donut-Signature"), os.environ["DONUT_WEBHOOK_SECRET"])- If your framework parses JSON for you, get the raw body instead. Parsing and serializing again changes the bytes, and the signature no longer matches. In Express, for example, use
express.raw({ type: 'application/json' })on the webhook route. - After the signature matches, parse the body with a parser that keeps 64-bit IDs exact. See IDs are 64-bit integers.
X-Donut-Delivery-Idstays the same when DonutChat retries a delivery. Use it to skip deliveries you’ve already handled.- Regenerating the secret invalidates the old one immediately, so update your endpoint with the new secret right away.
Keep credentials safe
Section titled “Keep credentials safe”- Store tokens, webhook URLs and signing secrets in environment variables or a secrets manager, and keep them out of source control.
- Use them only in server-side code. Never put them in a web page, a mobile app or other code that runs on someone else’s device.
- Don’t paste them into chats, issues or support requests.
- If one may have leaked, replace it right away.