API Reference
Webhooks
Real-time HTTPS POST notifications when messages arrive, delivery status changes, or Device/QR channels connect or disconnect.
How delivery works
- Transport — HTTPS POST with JSON body
- Queue — Asynchronous with automatic retries
- Timeout — 8s per attempt
- Retries — Up to 5 attempts with exponential backoff (60s → 5m → 15m → 1h → 2h)
- Auto-disable — After 10 consecutive failures the endpoint is disabled until re-enabled
Unified across channels
Cloud API and Device/QR share the same event names and payload format. Register once via
POST /v1/webhooks or the dashboard.
Events
Subscribe to exact names (message.received) and/or families (message.*, device.*). Leave events empty or null to receive all events. There is no message.created event — inbound is message.received.
| Event | Channels | Description |
|---|---|---|
message.* |
cloud, device | Family wildcard — matches message.received, message.sent, message.delivered, message.read, and message.failed. |
message.received |
cloud, device | A new inbound message was received (Cloud API or Device/QR channel). |
message.sent |
cloud, device | An outbound message was accepted and sent from your account. |
message.delivered |
cloud, device | An outbound message was delivered to the recipient device. |
message.read |
cloud, device | An outbound message was read by the recipient. |
message.failed |
cloud, device | An outbound message failed to send or deliver. |
device.* |
device, cloud | Family wildcard — matches device.connected, device.disconnected, and device.status_changed. |
device.connected |
device | A Device/QR channel connected successfully (WhatsApp linked). |
device.disconnected |
device | A Device/QR channel lost its WhatsApp connection. |
device.status_changed |
cloud, device | Any channel connection-status transition (payload includes from_status and to_status). Fired in addition to device.connected/device.disconnected. |
ticket.* |
cloud, device | Family wildcard — matches ticket.created, ticket.updated, ticket.closed, ticket.reopened, and ticket.csat. |
ticket.created |
cloud, device | A tenant ticket was created. |
ticket.updated |
cloud, device | A tenant ticket was updated. |
ticket.closed |
cloud, device | A tenant ticket was closed. |
ticket.reopened |
cloud, device | A tenant ticket was reopened. |
ticket.csat |
cloud, device | A CSAT score was recorded on a tenant ticket. |
catalog.* |
cloud, device | Family wildcard — matches catalog.item.*, catalog.category.* and catalog.items.bulk_upserted. |
catalog.item.created |
cloud, device | A catalog item was created (panels or API). Payload: item. |
catalog.item.updated |
cloud, device | A catalog item changed. Payload: item. |
catalog.item.deleted |
cloud, device | A catalog item was deleted. Payload: item. |
catalog.category.created |
cloud, device | A catalog category was created. Payload: category. |
catalog.category.updated |
cloud, device | A catalog category changed. Payload: category. |
catalog.category.deleted |
cloud, device | A catalog category was deleted. Payload: category. |
catalog.items.bulk_upserted |
cloud, device | A bulk upsert finished. Payload: data = one result per record (id, external_id, status). No per-item events are sent for it. |
knowledge.* |
cloud, device | Family wildcard — matches knowledge.entry.created, .updated, .deleted and knowledge.entries.bulk_upserted. |
knowledge.entries.bulk_upserted |
cloud, device | A knowledge bulk upsert finished. Payload: data = one result per record. No per-entry events are sent for it. |
knowledge.entry.created |
cloud, device | A knowledge entry was created. Payload: entry. |
knowledge.entry.updated |
cloud, device | A knowledge entry changed. Payload: entry. |
knowledge.entry.deleted |
cloud, device | A knowledge entry was deleted. Payload: entry. |
data_source.* |
cloud, device | Family wildcard — matches data_source.sync.finished and data_source.sync.failed. |
data_source.sync.finished |
cloud, device | A data source run finished. Payload: data = source id, name and run counts. No per-item events are sent for a run. |
data_source.sync.failed |
cloud, device | A data source run failed. Payload: data = source id, name and the reason. |
number.* |
cloud, device | Family wildcard — matches number.dual_transport_merged, number.dual_transport_suspended and number.dual_transport_resumed. |
number.dual_transport_merged |
cloud, device | A number's official and QR connections were merged into one channel. Payload: data = number id, both connection ids, policy. |
number.dual_transport_suspended |
cloud, device | Free (QR) sending on a merged number stopped (kill switch or a provider warning). Payload: data = number id, reason. |
number.dual_transport_resumed |
cloud, device | Free (QR) sending on a merged number resumed. Payload: data = number id. |
template.* |
cloud | Family wildcard — matches template.created, template.submitted, template.status.updated, template.rejected, template.approved, template.deleted, and template.sync.failed. |
template.created |
cloud | A Meta template was created and submitted. |
template.submitted |
cloud | A Meta template was submitted for review. |
template.status.updated |
cloud | Meta updated a template status. |
template.rejected |
cloud | Meta rejected a template. |
template.approved |
cloud | Meta approved a template. |
template.deleted |
cloud | A Meta template was deleted. |
template.sync.failed |
cloud | Template sync with Meta failed. |
Request envelope
JSON
{
"event": "message.received",
"timestamp": "2026-07-10T12:00:00+03:00",
"data": { }
}
| Field | Type | Description |
|---|---|---|
event | string | Event name from the table above |
timestamp | ISO 8601 | When Washeej generated this delivery |
data | object | Event payload — structure depends on event type |
Real-time vs webhooks
| Mechanism | Audience | Use case |
|---|---|---|
| Webhooks | Your server | Message events, delivery status, device connection |
| Pusher (WebSocket) | Washeej web inbox only | Live UI updates — not part of the public API |