Wassenger webhooks
Agent Skills for Wassenger — automate WhatsApp Business (official WABA) by chatting with Claude, Cursor, Codex or any Agent Skills client. 22 skills + the Wassenger MCP server.
npx -y skills add wassengerhq/whatsapp-skills --skill wassenger-webhooksAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Subscribe to real-time WhatsApp events from Wassenger — inbound messages, delivery and read receipts, status changes, campaign events, chat assignment changes, device state, and more. Use when the user wants live automation triggered by WhatsApp events (auto-replies, CRM sync, alerting, opt-out handling, multi-agent routing). Covers subscription via the REST API, payload shapes, HMAC signature verification, retries, and how to expose a public endpoint locally for development.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
9.2 KB, ~2.3k tokens by cl100k_base, as published. Nobody here has run it
Wassenger Webhooks
Drive automations from real WhatsApp events instead of polling. Webhooks turn "send when X happens" requests into a couple of API calls plus a small HTTP handler.
When to use
- The user says "trigger something when a message arrives", "notify me when a campaign finishes", "auto-reply", "sync to my CRM", "ping Slack on every reply".
- Another skill (
wassenger-customer-support,wassenger-campaignsopt-out,wassenger-ecommerceorder events) needs live event ingestion. - The user asks how to receive delivery / read receipts.
For ad-hoc polling ("show me the last 10 messages"), use wassenger-inbox / wassenger-messaging instead — webhooks are for production automation.
Prerequisites
wassenger-setupcomplete.- A publicly reachable HTTPS endpoint that accepts
POSTrequests with a JSON body. For local development, expose it viangrok,cloudflared,bore.pub, or similar. - An idempotency strategy on your side — Wassenger retries failed deliveries (up to ~24h), so the same event can land twice.
Architecture
WhatsApp ──► Wassenger Cloud ──► [Webhook POST] ──► Your endpoint ──► Your logic
│
└── HMAC SHA-256 in X-Wassenger-Signature header
The MCP server does not expose subscribing to webhooks — webhook management is a REST-only flow. Call the REST API directly from a small script or curl.
Step 1 — Pick the events to receive
Common event types (full list at https://app.wassenger.com/docs/#tag/Webhooks):
| Event | Fires when |
|---|---|
message:in:new | Inbound message received |
message:out:sent | Outbound message sent successfully |
message:out:delivered | Delivery receipt from WhatsApp |
message:out:read | Read receipt |
message:out:failed | Send failed (number invalid, blocked, quota, …) |
chat:assigned | Chat assigned to a team member |
chat:status:changed | Chat status changed (active/pending/resolved/archived) |
device:status:changed | Device connection state changed |
campaign:started / campaign:finished | Campaign lifecycle |
contact:created / contact:updated | Contact synced into Wassenger |
Subscribe to the least you need — every event you don't filter is bandwidth and a retry-storm risk.
Verify the exact names. Confirm the precise event-name strings (and the signature header name/format used in Step 3) against the live Webhooks reference at https://app.wassenger.com/docs/#tag/Webhooks before hardcoding them — they can differ from the examples here.
Step 2 — Subscribe
curl -X POST https://api.wassenger.com/v1/webhooks \
-H "Token: $WASSENGER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "CRM sync",
"url": "https://hooks.example.com/wassenger",
"events": ["message:in:new", "message:out:read"],
"active": true,
"device": "DEVICE_ID_OR_OMIT_FOR_ACCOUNT_WIDE"
}'
Response includes the webhook id and a secret. Store the secret — you need it to verify signatures.
To list, update, or delete webhooks: GET / PATCH / DELETE /v1/webhooks/{id}.
Step 3 — Verify the signature
Every delivery includes X-Wassenger-Signature: sha256=<hex>. Compute HMAC-SHA-256 over the raw body using the webhook's secret and compare.
Node.js example:
import crypto from 'node:crypto'
function verifyWassengerWebhook (rawBody, signatureHeader, secret) {
if (!signatureHeader?.startsWith('sha256=')) return false
const expected = signatureHeader.slice('sha256='.length)
const actual = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
// Constant-time compare
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(actual, 'hex')
)
}
Always verify. An unverified endpoint is trivially attackable — anyone with the URL can spoof events.
Step 4 — Handle the payload
Typical inbound-message payload:
{
"id": "evt_01HXXX",
"event": "message:in:new",
"createdAt": "2026-05-27T14:32:11.218Z",
"device": "DEVICE_ID",
"data": {
"message": {
"id": "msg_01HXXX",
"chat": "[email protected]",
"from": "34600111222",
"to": "DEVICE_PHONE",
"fromMe": false,
"type": "text",
"body": "Hi, I need help with order #1234",
"timestamp": "2026-05-27T14:32:10.000Z"
},
"chat": {
"id": "[email protected]",
"contact": { "name": "Marta L.", "phone": "+34600111222" },
"status": "active",
"assignedTo": null
}
}
}
Respond with HTTP 2xx within 10 seconds. Wassenger considers anything else (including timeouts) a failure and will retry with exponential backoff up to ~24h.
Recipes
Recipe 1 — Auto-reply to first contact
on event: message:in:new
if first contact (this chat WID not seen before in your store):
send_whatsapp_message
- device: event.device
- action: "text"
- chat: data.message.chat # the WID, e.g. "[email protected]"
- message: "Hi! Thanks for reaching out. An agent will reply within 1 hour."
mark chat WID as seen in your store
ack 200
Decide "first contact" from your own store (track which chat WIDs you've greeted), and dedupe on data.message.id so a webhook retry can't double-fire. Don't infer it from a per-chat message count — that field isn't a reliable first-contact signal.
Recipe 2 — Slack alert on every reply
on event: message:in:new
body = data.message.body
contact = data.chat.contact.name || data.message.from
POST https://hooks.slack.com/... body: {
text: `📩 *${contact}*: ${body}`
}
ack 200
Recipe 3 — Opt-out handling (STOP keyword)
on event: message:in:new
if data.message.body matches /^(STOP|UNSUBSCRIBE|BAJA)$/i:
send_whatsapp_message
- action: "agent"
- chat: data.message.chat
- agentId: <userId>
- message: "You've been unsubscribed. Reply START to opt back in."
- actions: [{ action: "labels:add", params: { labels: ["opted-out"] } }]
add to suppression list in your DB
ack 200
(Applying a label is an agent-action side effect on the send — manage_whatsapp_labels is CRUD only and can't attach a label to a chat.)
Recipe 4 — Sync delivery receipts to a CRM
on event in [message:out:delivered, message:out:read, message:out:failed]:
PATCH /crm/messages/<message.reference> with status=event.type
ack 200
message.reference is the custom tracking ID you set on the send (send_whatsapp_message's reference parameter), echoed back on the message; store it to bridge Wassenger IDs to your CRM IDs.
Recipe 5 — Local dev with ngrok
# Terminal 1
node webhook-handler.js # listens on :3000
# Terminal 2
ngrok http 3000
# → https://abcd-12-34-56-78.ngrok-free.app
# Terminal 3 — subscribe
curl -X POST https://api.wassenger.com/v1/webhooks \
-H "Token: $WASSENGER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Local dev",
"url": "https://abcd-12-34-56-78.ngrok-free.app/wassenger",
"events": ["message:in:new"]
}'
Send yourself a WhatsApp message — the event should arrive in ngrok's inspector. Delete the webhook (DELETE /v1/webhooks/{id}) when done; ngrok URLs rotate.
Common pitfalls
- Forgetting to verify signatures. Public URLs leak. Without HMAC verification, anyone can POST fake events and trigger your automations.
- Returning 4xx/5xx for normal cases. If the event isn't relevant to you, still return
200—4xxtriggers retries for ~24h. - Reading the body twice. If you parse JSON before signature verification, you've already discarded the raw bytes you need. Capture the raw body first (
express.raw(),bodyParser.raw()), verify, then parse. - Subscribing to everything. Each event you receive but don't handle is wasted bandwidth and potential retries. Subscribe narrowly.
- No idempotency. Retries deliver the same
event.idtwice. Dedupe onevent.idin your DB or use idempotent operations. - Localhost as the URL. Wassenger sends from the public internet —
http://localhost:3000will never reach you. Always tunnel for dev.
See also
wassenger-messaging— for the actions your handler triggers (sending replies).wassenger-inbox— for the chat / label / assignment side effects.wassenger-customer-support— opinionated SLA + auto-reply playbook on top of webhooks.wassenger-campaigns— opt-out webhook pattern in detail.- Full event reference: https://app.wassenger.com/docs/#tag/Webhooks
Gives 0 of the 12 instructions most apis services skills give in ~2.3k tokens
Counted across 424 of the 426 authors here whose files we hold, read 2026-08-06
- use plural nouns for resource namesin 41 of 424, across 32 files
- use cursor-based pagination for large datasetsin 35 of 424, across 20 files
- include rate limit headers in responsesin 25 of 424, across 13 files
- Use kebab-case for multi-word resourcesin 23 of 424, across 13 files
- version APIs in the URL pathin 19 of 424, across 9 files
- use semantic HTTP status codesin 18 of 424, across 8 files
- verify webhook signaturesin 18 of 424, across 11 files
- use query parameters for filteringin 17 of 424, across 6 files
- use async database operationsin 14 of 424, across 7 files
- wrap successful responses in a data fieldin 13 of 424, across 3 files
- prefix sorting parameters with a hyphen for descending orderin 13 of 424, across 3 files
- set appropriate HTTP status codesin 13 of 424, across 6 files
Said here and by no other author read
- call the rest api directly to manage webhooks
- respond with a success status within ten seconds
- return success status for ignored events
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.