{
"api_version": "v1",
"webhook_version": "2026-09-02",
"event_type": "participant.added",
"event_id": "evt_4a8c2e6b9d1f",
"created_at": "2026-10-14T18:00:00.100Z",
"trace_id": "trc_4a8c2e6b9d1f",
"workspace_id": "workspace_abc123",
"subscription_id": "whsub_abc123",
"data": {
"chat_id": "chat_abc123",
"phone_number_id": "phone_abc123",
"occurred_at": "2026-10-14T18:00:00.000Z",
"participant": {
"type": "phone",
"handle": "+15555550123",
"address": "+15555550123",
"status": "active",
"joined_at": "2026-10-14T18:00:00.050Z",
"left_at": null
}
}
}Webhook event
Blujay sends each event as an HTTPS POST with a JSON body to the URL on your subscription. Respond with any 2xx status to acknowledge it. You can see every delivery attempt with GET /v1/webhook-subscriptions/{subscription_id}/deliveries.
Verify the signature. Webhooks are signed following the Standard Webhooks specification, so you can use any Standard Webhooks library. To verify by hand:
- Take the
webhook-idandwebhook-timestampheaders and the raw request body, exactly as received. - Build the signed content:
{webhook-id}.{webhook-timestamp}.{body}. - Remove the
whsec_prefix from your subscription’ssigning_secretand base64-decode the rest. That is your key. - Compute an HMAC-SHA256 of the signed content with the key, and base64-encode the result.
- Compare
v1,<your result>with thewebhook-signatureheader using a constant-time comparison. The Standard Webhooks format allows several space-separated signatures in this header, so accept the request if any of them matches.
Also reject requests whose webhook-timestamp is more than about five minutes from your server’s clock.
Handle duplicates. The same event can arrive more than once. webhook-id equals the body’s event_id and stays the same for every delivery of an event, so use it to skip events you’ve already processed.
{
"api_version": "v1",
"webhook_version": "2026-09-02",
"event_type": "participant.added",
"event_id": "evt_4a8c2e6b9d1f",
"created_at": "2026-10-14T18:00:00.100Z",
"trace_id": "trc_4a8c2e6b9d1f",
"workspace_id": "workspace_abc123",
"subscription_id": "whsub_abc123",
"data": {
"chat_id": "chat_abc123",
"phone_number_id": "phone_abc123",
"occurred_at": "2026-10-14T18:00:00.000Z",
"participant": {
"type": "phone",
"handle": "+15555550123",
"address": "+15555550123",
"status": "active",
"joined_at": "2026-10-14T18:00:00.050Z",
"left_at": null
}
}
}Authorizations
Your workspace API key, sent as Authorization: Bearer blu_.... Workspace admins can find it in the Blujay app under API.
An API key has full access to your workspace and works on every endpoint. There is no test mode: every key sends real messages and changes real data, so keep it secret and use it only from your server.
Each API key can make 600 read requests and 120 write requests per minute, plus 60 attachment requests per minute. Over a limit you get 429 RATE_LIMIT_EXCEEDED with Retry-After.
Headers
The event's unique ID (evt_...), the same as event_id in the body.
When this delivery was signed, in Unix seconds.
v1, followed by the base64 HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body}, signed with your base64-decoded whsec_ secret.
Body
The payload version for a subscription's events.
2026-08-20reports the service you asked for asrequested_serviceand the service actually used asservice.2026-09-02adds messageparts.2026-10-03adds iMessage group typing with aparticipantssnapshot, andreply_toon message events.
Earlier versions keep their original payload shapes and never receive group typing. Each event uses the subscription's version at the time the event was created, so changing the version affects only later events.
2026-06-14, 2026-08-01, 2026-08-20, 2026-09-02, 2026-10-03 Every event type that can appear on a subscription or in a webhook. Some types are recognized only so older subscriptions keep working and may never be sent. You can add only the types in WebhookSubscribableEventType.
message.accepted, message.sent, message.delivered, message.read, message.failed, message.received, reaction.added, reaction.removed, participant.added, participant.removed, chat.group_name_updated, phone_number.status_updated, contact.tag_added, contact.assigned, chat.typing_indicator.started, chat.typing_indicator.stopped, message.edited, message.unsent ^evt_[A-Za-z0-9_-]+$"evt_abc123"
"2026-10-14T17:01:00.000Z"
^trc_[A-Za-z0-9_-]+$"trc_8f2a1c0e4b7d"
^workspace_[A-Za-z0-9_-]+$"workspace_abc123"
^whsub_[A-Za-z0-9_-]+$"whsub_abc123"
The event's details. The shape depends on event_type and the subscription's payload version.
- Message events on version
2026-09-02and later includeparts. A media part gives theattachment_id, file details and adownload_url. - Group
message.receivedevents includesenderon every version. - Version
2026-10-03addsreply_toto every message event (the same object as in message reads, or null). Earlier versions never include it. message.failedevents may include afailure_codeyou can act on. On version2026-08-01and later they also includefailurewith acodeand a readablereason.failure.codeis one ofnetwork_rejected(the messaging network rejected the message),service_mismatch,sender_unavailable,sender_retired,recipient_unavailable,reply_requires_imessage,dispatch_unavailable,unsupported_content(content the selected service can't send, such as a file type or size it doesn't carry) ordelivery_failed.
Response
Return any 2xx status to acknowledge the event.