Skip to content
donutchat.com

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.

CredentialUsed forLooks likeWhere you get it
Bot tokenBot HTTP API and event streamdonutbot_…Shown once when you create an API bot
Webhook URLIncoming webhookshttps://donutchat.com/webhooks/v1/{webhook_id}/{token}Shown once when you create the webhook
Signing secretChecking event webhook deliverieswhsec_…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.

Send the token in the Authorization header of every bot API request:

Authorization: Bearer donutbot_YOUR_TOKEN_HERE
  • Write Bearer with this exact capitalization, then one space and the whole token, including donutbot_.
  • 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.
Terminal window
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.

HTTPerrormessageCause
401unauthorizedMissing or invalid Authorization header. Use: Authorization: Bearer <token>The header is missing or doesn’t start with Bearer and a space.
401unauthorizedInvalid bot tokenThe 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.

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_HERE

DonutChat 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 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.

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 unauthorized and the message Invalid 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.

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:

  1. Read the request body as raw bytes, before any JSON parsing.
  2. Compute the HMAC-SHA256 of those bytes with your signing secret as the key, and hex-encode it in lowercase.
  3. Put sha256= in front and compare the result with the X-Donut-Signature header, using a constant-time comparison.
  4. 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)
  • 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-Id stays 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.
  • 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.