Sendable Docs

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

  1. Go to your session
  2. Open the Webhooks tab
  3. Click Add Webhook
  4. Enter your URL
  5. Choose subscribed event types
  6. 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

EventWhen It FiresUse Case
message.sentMessage queuedTrack outbound traffic
message.deliveredMessage deliveredConfirm delivery
message.readRecipient read messageTrack engagement
message.failedDelivery failedRetry or alert
message.receivedDirect incoming messageStore or respond
message-personal.receivedPersonal chat messagePersonal inbox flows
message-group.receivedGroup or community messageGroup automations

Session Events

EventWhen It FiresUse Case
session.socket-connectedSession goes onlineResume sending
session.socket-disconnectedSession goes offlineAlert team, pause sending

Security

Signature Verification

Sendable uses Svix-style signatures. Requests include:

  • svix-id
  • svix-timestamp
  • svix-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

On this page