Webhook Events
Complete reference for Sendable webhook events and payloads.
Envelope Shape
Every webhook uses the same top-level envelope:
{
"createdAt": "2026-03-02T10:30:00.000Z",
"type": "message.sent",
"data": {}
}Event Types
Message Events
message.sent
Triggered when a message is queued for delivery.
{
"createdAt": "2026-03-02T10:30:00.000Z",
"type": "message.sent",
"data": {
"id": "wamid.HBgLN...",
"type": "text",
"timestamp": 1740911400,
"origin": "personal",
"direction": "OUTBOUND",
"to": "[email protected]",
"chatId": "[email protected]",
"status": "PENDING",
"text": {
"content": "Hello from Sendable"
}
}
}message.delivered
Triggered when a sent message is delivered.
{
"createdAt": "2026-03-02T10:30:05.000Z",
"type": "message.delivered",
"data": {
"id": "wamid.HBgLN...",
"type": "text",
"timestamp": 1740911400,
"origin": "personal",
"direction": "OUTBOUND",
"to": "[email protected]",
"chatId": "[email protected]",
"status": "DELIVERED",
"text": {
"content": "Hello from Sendable"
}
}
}message.read
Triggered when the recipient reads a message.
{
"createdAt": "2026-03-02T10:35:00.000Z",
"type": "message.read",
"data": {
"id": "wamid.HBgLN...",
"type": "text",
"timestamp": 1740911400,
"origin": "personal",
"direction": "OUTBOUND",
"to": "[email protected]",
"chatId": "[email protected]",
"status": "READ",
"text": {
"content": "Hello from Sendable"
}
}
}message.failed
Triggered when delivery fails.
{
"createdAt": "2026-03-02T10:30:10.000Z",
"type": "message.failed",
"data": {
"id": "wamid.HBgLN...",
"type": "text",
"timestamp": 1740911400,
"origin": "personal",
"direction": "OUTBOUND",
"to": "[email protected]",
"chatId": "[email protected]",
"status": "FAILED",
"text": {
"content": "Hello from Sendable"
}
}
}message.received
Triggered when an incoming direct message is received. Group and community traffic may also arrive as message-personal.received or message-group.received depending on the chat origin.
{
"createdAt": "2026-03-02T10:40:00.000Z",
"type": "message.received",
"data": {
"id": "wamid.HBgLM...",
"type": "text",
"timestamp": 1740912000,
"origin": "personal",
"direction": "INBOUND",
"from": "[email protected]",
"to": "[email protected]",
"senderJid": "[email protected]",
"chatId": "[email protected]",
"status": "PENDING",
"text": {
"content": "Hello!"
}
}
}Session Events
session.socket-connected
Triggered when the WhatsApp socket connects successfully.
{
"createdAt": "2026-03-02T10:00:00.000Z",
"type": "session.socket-connected",
"data": {
"sessionId": "sess_xyz789",
"socketStatus": "connected",
"status": "authenticated"
}
}session.socket-disconnected
Triggered when the WhatsApp socket disconnects.
{
"createdAt": "2026-03-02T10:15:00.000Z",
"type": "session.socket-disconnected",
"data": {
"sessionId": "sess_xyz789",
"socketStatus": "disconnected",
"status": "authenticated",
"reason": 515
}
}Verifying Webhooks
Use the SDK with the raw request body plus the Svix headers:
import express from 'express'
import { Sendable } from '@sendable-dev/sdk'
const sendable = new Sendable({
apiKey: process.env.SENDABLE_API_KEY!,
apiKeyScope: 'session',
webhookSecret: process.env.SENDABLE_WEBHOOK_SECRET,
})
app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
const payload = req.body.toString()
try {
const event = await sendable.webhooks.verifyAndParse(payload, req.headers)
console.log(event.type, event.data)
return res.sendStatus(200)
} catch {
return res.status(401).send('Invalid signature')
}
})