Webhook events
The request format of Thunderclap's webhooks. To set them up and verify them, see the webhooks guide.
Request
| Part | Value |
|---|---|
| Method | POST |
Content-Type | application/json |
Thunderclap-Signature | t=<unix seconds>, then one v1=<hex> per active secret of the app |
| Body | A payload: { app, events } |
{
"app": "app_8f3k2m9q",
"events": [
{ "id": "4c1d…", "time": 1761040800000, "type": "channel_occupied", "channel": "room-42" },
{ "id": "9a7e…", "time": 1761040800120, "type": "member_added", "channel": "presence-room-42",
"userId": "ada", "info": { "name": "Ada" } }
]
}Common fields
Every event has these fields:
| Field | Type | Description |
|---|---|---|
id | string | Unique id. Retries may deliver an event twice: deduplicate on it |
time | number | When it happened, in milliseconds since epoch. Events can arrive out of order |
type | string | One of the types below |
channel | string | The channel it happened on |
Event types
type | Kind | Extra fields | Sent when |
|---|---|---|---|
channel_occupied | Channel existence | none | A channel got its first subscriber |
channel_vacated | Channel existence | none | A channel has had no subscribers for 5 seconds |
member_added | Presence | userId: string, info?: unknown | A user joined a presence channel |
member_removed | Presence | userId: string | A user's last connection left a presence channel |
client_event | Client events | event: string, data: unknown, socketId: string, userId?: string | A client sent an event; userId is set on presence channels |
cache_miss | Cache misses | none | A subscriber found a cache channel empty; once per channel until the next event |
An endpoint receives only the kinds it was set up with in the dashboard.
Signature
Thunderclap-Signature carries a timestamp and one signature per active secret, each the HMAC-SHA256 of <t>.<body> in hex:
t=1761040800,v1=5f2b…,v1=a91c…verifyWebhook in @thunderclapjs/server checks it for you: it accepts any of the signatures and refuses timestamps older than 5 minutes.