Sendable Docs

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')
  }
})

On this page