The Webhooks dashboard lives at wassist.app/developers/webhooks. Create one there, copy the signing secret, and you’re done.
How a delivery works
Behind the scenes:- The event is committed to our outbox as a
WebhookDeliverywith a stable UUID. - A worker POSTs the JSON body to your URL.
- We record an attempt row with status code, duration, and response body.
- On
5xxor network errors we retry up to 3 times with exponential backoff (10s, 30s, 90s). - After 20 consecutive failures the webhook is auto-disabled and you get a banner in the dashboard.
Headers
Verifying signatures
ComputeHMAC-SHA256 over the string <timestamp>.<raw body> using the webhook’s signing secret and compare it in constant time to the v1 component.
- Node.js
- Python
- Go
Event catalog
message.received
Fired when a customer sends a message to one of your numbers.
+. All IDs are Wassist IDs.
message.bodyholds the text, the media caption, or, for voice notes, a transcription.message.replyTois the Wassist ID of the message the customer quoted, ornull. To quote the customer’s message in your reply, passmessage.idasreplyTowhen you send a message.message.mediais a list of{ "url", "mimeType", "filename" }.filenameis set for documents andnullotherwise.- Audio items also carry
transcriptionandvoice.transcriptionmatchesbody: the transcript,"[unintelligible audio]"when nothing could be heard, ornullif transcription failed.voiceistruefor a recorded voice note andfalsefor an uploaded audio file.
audio/mpeg), video, and documents. Voice note, video and document events are sent once the file has downloaded (and, for voice notes, been transcribed), so they can arrive a few seconds after the message.
url is a pre-signed link that needs no API key and expires one hour after the message arrived. Download the file when you receive the event, or fetch a fresh link from GET /conversations/{id}/messages/.
Ad attribution (Click-to-WhatsApp)
When the customer starts the chat by tapping a Click-to-WhatsApp ad (or a boosted post),message.referral carries the ad details WhatsApp attached to that message. latestReferral has the same shape and holds the most recent referral in the conversation. That means later messages in an ad-driven chat can still be attributed to the ad.
ctwaClid is the Click-to-WhatsApp click ID. Send it back to Meta via the Conversions API for Business Messaging to attribute leads and purchases to the ad. sourceId is the Meta ad (or post) ID. You can also look these up later: they’re on referral for each message returned by GET /conversations/{id}/messages/, and on latestReferral for each conversation. To filter conversations, use hasReferral, referralSourceId or ctwaClid. See Ad Attribution for the full field reference and a Conversions API example.
message.status.updated
Fired once each time the delivery status of a message you sent changes. WhatsApp sometimes reports statuses twice or out of order. Those callbacks are dropped, so a status never goes backwards. Deduplicate on message.id plus message.status.
statusis one ofsent,delivered,readorfailed.failureis only present whenstatusisfailed.codeis Meta’s error code.
message.reaction.updated
Fired when the customer adds, changes or removes a reaction. reaction.messageId is the Wassist ID of the message they reacted to, and emoji is null when the reaction was removed. Reactions don’t trigger message.received, subscription.message.received, or the agent.
type: "reaction":
"emoji": null (or leave it out) to remove the reaction.
test.ping
Triggered by the dashboard Send test event button. Same envelope as a real event with a stub payload — use it to validate signature verification in CI.
Best practices
- Idempotency. Store the
X-Wassist-DeliveryID and reject duplicates. We use the same ID across all retries. - Respond fast. Send a
2xxwithin 10 seconds and do real work asynchronously. We treat slow responses as failures and retry. - Replay protection. Reject events where
X-Wassist-Timestampis more than 5 minutes from now. - Rotate secrets. From the dashboard, Rotate secret generates a new secret. The old one stops working immediately.
- Local development. Use the CLI:
wassist listentunnels real events tolocalhostwithout exposing a public URL.
Replaying deliveries
Every delivery is stored with its full payload. From the dashboard you can:- Filter deliveries by status (all / failed / succeeded).
- Open a delivery to see request headers, response body, and the timeline of every retry attempt.
- Click Replay to send a fresh copy of the same payload. It gets a new
X-Wassist-DeliveryID and is re-signed with a fresh timestamp, but the JSON body is identical, including itstimestampand any media URLs (which may have expired). Deduplicate replays onmessage.id.
POST /api/v1/webhook-deliveries/{id}/replay/.