Webhooks (Server)

This page describes what your application receives in the configured webhooks when Sendeasy processes channel/message events.

Scopes: general and per-channel

Webhooks have two possible scopes:

  • General: receives events from every channel of the company. Think of it as the "global" webhook for the account.
  • Channel-specific: receives events only from the channel it's bound to.

The two scopes coexist. For each event, Sendeasy fires the union: all active general webhooks of the company plus the per-channel webhook(s) for that channel. Configure as many of each scope as you want.

Example: a company has 2 WhatsApp instances and 1 Email mailbox. You can set up:

  • 1 webhook on URL A → only receives Sales WhatsApp events.
  • 1 webhook on URL B → only receives Support WhatsApp events.
  • 1 webhook on URL C → only receives Support Email events.

Configure under Settings → Integrations.

Flow summary

  1. Sendeasy processes a channel/message event.
  2. The payload is normalized to the public contract.
  3. Sendeasy looks up all enabled=true webhooks bound to the source channel.
  4. For each webhook found, it POSTs the URL with the payload as body. If a bearer token was configured, it goes in the Authorization header.

Base payload

FieldTypeDescription
eventstringevent name
channelstringchannel type: "whatsapp", "email", or "sms"
dataobjectevent-specific payload
channelIdstring | numberchannel identifier — UUID (WhatsApp) or numeric id (Email/SMS)
{
  "event": "messages.upsert",
  "channel": "whatsapp",
  "data": {},
  "channelId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Events per channel

WhatsApp (channel: "whatsapp" or "waba")

connection.update

{ "state": "open" }

Common values: open, close.

messages.upsert / messages.set / send.message

{
  "key": {
    "remoteJid": "5511999999999@s.whatsapp.net",
    "fromMe": false,
    "id": "3EB0XXXXXX"
  },
  "message": { "conversation": "Hi, I need help" },
  "messageTimestamp": "1634567890",
  "pushName": "Customer"
}

messages.update

{ "keyId": "3EB0XXXXXX", "fromMe": true, "status": 3 }

status: 1 (sent), 2 (delivered), 3 (read).

messages.delete

{ "id": "3EB0XXXXXX", "fromMe": false, "remoteJid": "5511999999999@s.whatsapp.net" }

call

{ "id": "CALL_ID_123", "from": "5511999999999@s.whatsapp.net", "status": "reject" }

WABA — messaging and message_status

{
  "event": "message_status",
  "channel": "whatsapp",
  "data": { "messageId": "wamid.HBg...", "status": "read", "timestamp": "1713800010" },
  "channelId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Email (channel: "email")

Fired when the bound mailbox receives an email.

{
  "event": "messaging",
  "channel": "email",
  "channelId": 7,
  "data": {
    "email": {
      "direction": "inbound",
      "mailbox": "support@company.com",
      "from": { "email": "customer@domain.com", "name": "Customer" },
      "to": ["support@company.com"],
      "subject": "Question about order #1234",
      "text": "Hello, I'd like to ask...",
      "html": "<p>...</p>",
      "messageId": "<abc@domain.com>",
      "receivedAt": "2026-04-28T13:45:00.000Z"
    }
  }
}

SMS (channel: "sms")

Fired when the bound number receives an SMS.

{
  "event": "messaging",
  "channel": "sms",
  "channelId": 15,
  "data": {
    "sms": {
      "direction": "inbound",
      "from": "+5511999999999",
      "body": "Confirm appointment",
      "providerPayload": { "...": "raw provider data" }
    }
  }
}

Consumption best practices

  1. Treat processing as asynchronous and implement retry with backoff.
  2. Use idempotency keyed on event + identifiers (key.id, keyId, data.email.messageId, …).
  3. Store the raw payload for troubleshooting.
  4. Validate the Authorization header when configured on the webhook.
  5. Keep timeouts short (≤ 5s) — if Sendeasy doesn't get a response in 5s, the webhook is treated as failed and the event is dropped (no automatic retry).

Where to manage

Webhooks are configured and activated under Settings → Integrations on the platform.

Essa informação foi útil?