Webhooks Dashboard
Configure and manage webhooks for real-time event notifications.
What Are Webhooks?
Webhooks are HTTP callbacks Sendable pushes to your application when subscribed events happen, such as:
- outgoing message status changes
- incoming messages
- session socket connect or disconnect events
This is the recommended way to react to delivery state and inbound traffic without polling.
Setting Up Webhooks
Step 1: Create an Endpoint
Your application needs a public HTTPS endpoint that accepts POST requests and preserves the raw body for signature verification.
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)
handleEvent(event).catch(console.error)
return res.sendStatus(200)
} catch {
return res.status(401).send('Invalid signature')
}
})Step 2: Add the Webhook in Dashboard
- Go to your session
- Open the
Webhookstab - Click
Add Webhook - Enter your URL
- Choose subscribed event types
- Save
Step 3: Save the Secret
Every endpoint has a Svix signing secret in the form whsec_.... Store it in SENDABLE_WEBHOOK_SECRET and never expose it to the browser.
Event Types
Message Events
| Event | When It Fires | Use Case |
|---|---|---|
message.sent | Message queued | Track outbound traffic |
message.delivered | Message delivered | Confirm delivery |
message.read | Recipient read message | Track engagement |
message.failed | Delivery failed | Retry or alert |
message.received | Direct incoming message | Store or respond |
message-personal.received | Personal chat message | Personal inbox flows |
message-group.received | Group or community message | Group automations |
Session Events
| Event | When It Fires | Use Case |
|---|---|---|
session.socket-connected | Session goes online | Resume sending |
session.socket-disconnected | Session goes offline | Alert team, pause sending |
Security
Signature Verification
Sendable uses Svix-style signatures. Requests include:
svix-idsvix-timestampsvix-signature
The SDK also accepts white-labeled webhook-* header names if your endpoint is configured that way.
You should verify against the raw request body before parsing JSON:
const payload = req.body.toString()
const event = await sendable.webhooks.verifyAndParse(payload, req.headers)Best Practices
Respond Quickly
Return HTTP 200 after verification and push real work into background jobs when possible.
Handle Duplicates
Webhook delivery is at-least-once, so build idempotency around the envelope:
async function handleEvent(event) {
const key = `${event.type}-${event.data.id ?? event.data.sessionId}-${event.createdAt}`
if (await wasProcessed(key)) {
return
}
await processEvent(event)
}Log Failures
async function processEvent(event) {
if (event.type === 'message.failed') {
console.error('message failed', event.data.id, event.data.status)
}
}Use Cases
Delivery Tracking
if (event.type === 'message.delivered') {
await updateOrderStatus(event.data.id, 'delivered')
}Incoming Chat Automation
if (event.type === 'message.received') {
await saveMessage(event.data)
await maybeAutoReply(event.data.chatId, event.data.text?.content)
}Session Monitoring
if (event.type === 'session.socket-disconnected') {
await notifyOps(`Session ${event.data.sessionId} disconnected`)
}Troubleshooting
Invalid Signature
- Use the raw body, not
JSON.stringify(req.body) - Verify the
whsec_...secret matches the endpoint in the dashboard - Make sure body parsing middleware is not running before verification
Missing Events
- Confirm the webhook is active
- Confirm the event type is subscribed
- Check delivery logs in the dashboard