Sendable Docs

Webhooks

Receive real-time message and session events from Sendable.

How Webhooks Work

  1. You configure a webhook URL in your session settings.
  2. Sendable sends HTTP POST requests to your URL when subscribed events occur.
  3. Your application verifies the Svix signature, processes the event, and returns HTTP 200 quickly.

Setting Up Webhooks

1. Create an Endpoint

Your endpoint must:

  • Accept POST requests
  • Preserve the raw request body for signature verification
  • Return HTTP 200 quickly, ideally in under 5 seconds
  • Handle duplicate events because delivery is at-least-once

Example in Express:

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()

  let event
  try {
    event = await sendable.webhooks.verifyAndParse(payload, req.headers)
  } catch {
    return res.status(401).send('Invalid signature')
  }

  processEvent(event).catch(console.error)
  res.sendStatus(200)
})

2. Configure in Dashboard

  1. Go to your session
  2. Navigate to Webhooks
  3. Click Add Webhook
  4. Enter your URL
  5. Select event types
  6. Save

3. Keep the Secret Safe

Each webhook endpoint has a Svix signing secret in the form whsec_.... Store it securely and pass it into the SDK as webhookSecret.

Event Types

Message Events

EventDescription
message.sentMessage queued for delivery
message.deliveredMessage delivered to recipient
message.readRecipient read the message
message.failedMessage delivery failed
message.receivedIncoming direct message received
message-personal.receivedIncoming personal-chat message received
message-group.receivedIncoming group or community message received

Session Events

EventDescription
session.socket-connectedWhatsApp socket connected successfully
session.socket-disconnectedWhatsApp socket disconnected

See Webhook Events for payload examples.

Security

Verify Svix Signatures

Sendable delivers webhooks with Svix headers:

  • svix-id
  • svix-timestamp
  • svix-signature

Professional and Enterprise setups may use white-labeled webhook-* header names. The SDK accepts both forms.

The safest path is to verify and parse in one step:

const event = await sendable.webhooks.verifyAndParse(payload, req.headers)

If you want to separate verification from parsing, use:

await sendable.webhooks.assertSignature(payload, req.headers)
const event = JSON.parse(payload)

Use HTTPS

Always use HTTPS for production webhook endpoints.

Best Practices

Respond Quickly

Do the verification work inline, then hand off processing:

app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const payload = req.body.toString()
  const event = await sendable.webhooks.verifyAndParse(payload, req.headers)

  queueWebhook(event).catch(console.error)
  res.sendStatus(200)
})

Handle Retries

Webhook delivery is at-least-once. If your endpoint fails or times out, Sendable will retry.

Use Idempotency

Use a stable key built from the envelope:

async function processEvent(event) {
  const id = event.data.id ?? event.data.sessionId
  const idempotencyKey = `${event.type}-${id}-${event.createdAt}`

  if (await alreadyProcessed(idempotencyKey)) {
    return
  }

  await handleEvent(event)
}

Log the Envelope

console.log('webhook', {
  type: event.type,
  createdAt: event.createdAt,
  id: event.data.id ?? event.data.sessionId ?? null,
})

Troubleshooting

Invalid Signature

  • Confirm you are passing the raw request body, not JSON.stringify(req.body)
  • Confirm SENDABLE_WEBHOOK_SECRET matches the endpoint secret from the dashboard
  • Confirm your framework has not consumed or transformed the body before verification

Missing Events

  • Confirm the webhook is active
  • Confirm the subscribed event type matches the event you expect
  • Check the delivery logs in the dashboard

Timeouts

  • Return HTTP 200 immediately after verification
  • Move heavy work into a queue or background job

On this page