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
- Sendeasy processes a channel/message event.
- The payload is normalized to the public contract.
- Sendeasy looks up all
enabled=truewebhooks bound to the source channel. - For each webhook found, it
POSTs the URL with the payload as body. If a bearer token was configured, it goes in theAuthorizationheader.
Base payload
| Field | Type | Description |
|---|---|---|
event | string | event name |
channel | string | channel type: "whatsapp", "email", or "sms" |
data | object | event-specific payload |
channelId | string | number | channel 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
- Treat processing as asynchronous and implement retry with backoff.
- Use idempotency keyed on
event+ identifiers (key.id,keyId,data.email.messageId, …). - Store the raw payload for troubleshooting.
- Validate the
Authorizationheader when configured on the webhook. - 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.