Webhook Events Reference
This page lists all available webhook events, their triggers, and payload formats. Use this as a reference when building your webhook handler.
Payload Envelope
Every webhook delivery includes a standard envelope with event-specific data in the data field:
{
"id": "evt_abc123def456",
"type": "message.received",
"event_id": "evt_abc123def456",
"created_at": "2026-03-04T10:30:00Z",
"tenant_id": "tenant_abc123",
"data": {
// Event-specific fields
}
}
| Field | Type | Description |
|---|---|---|
id | string | Unique event ID (for deduplication) |
type | string | Event type (e.g., message.received) |
event_id | string | Alias for id, included for convenience |
created_at | string | ISO 8601 timestamp |
tenant_id | string | Your tenant ID |
data | object | Event-specific payload |
Message Events
All message events (message.received, message.sent, message.delivered, message.read, message.failed) share the same enriched payload structure. The data object always contains the sub-objects message, conversation, and contact (the contact includes its contact_methods[]), plus a top-level contact_method object identifying the single contact method used for that message. This gives your webhook handler complete context without requiring follow-up API calls. (The examples for message.delivered/message.read/message.failed below abbreviate contact and omit contact_method for brevity — at runtime they carry the same enriched shape as message.sent.)
For detailed payload examples of every message type (images, videos, location pins, contact cards, interactive replies, reactions, templates, carousels, and more), see the Message Types Reference.
message.received
Triggered when a new inbound message arrives from a contact.
{
"id": "evt_msg_001",
"type": "message.received",
"event_id": "evt_msg_001",
"created_at": "2026-03-04T10:30:00Z",
"tenant_id": "tenant_abc123",
"data": {
"message": {
"id": "msg_abc123",
"conversation_id": "conv_xyz789",
"platform": "whatsapp",
"channel_id": "ch_123",
"contact_id": "ct_456",
"contact_method_id": "cm_789",
"direction": "inbound",
"message_type": "image",
"text": null,
"attachments": [
{
"id": "att_abc123",
"type": "image",
"filename": "whatsapp_image_abc123.jpg",
"content_type": "image/jpeg",
"file_size": 245000,
"url": "https://api.sendseven.com/api/v1/attachments/att_abc123/download",
"signed_url": "https://storage.googleapis.com/sendseven-attachments/tenant_abc123/att_abc123.jpg?X-Goog-Signature=..."
}
],
"status": "received",
"from_id": "+1234567890",
"to_id": null,
"external_id": "wamid.abc123",
"meta": {},
"created_at": "2026-03-04T10:30:00Z",
"sent_at": null,
"delivered_at": null,
"read_at": null
},
"conversation": {
"id": "conv_xyz789",
"channel_id": "ch_123",
"contact_id": "ct_456",
"status": "open",
"subject": null,
"created_at": "2026-03-04T10:29:00Z"
},
"contact": {
"id": "ct_456",
"name": "John Doe",
"email": "[email protected]",
"phone": "+1234567890",
"contact_methods": [
{
"id": "cm_789",
"method_type": "whatsapp_id",
"value": "1234567890",
"channel_id": null,
"display_name": null,
"phone_type": null,
"is_primary": true,
"email_status": null,
"last_used_at": null,
"created_at": "2026-03-04T10:29:00Z"
},
{
"id": "cm_790",
"method_type": "phone",
"value": "+1234567890",
"channel_id": null,
"display_name": null,
"phone_type": "mobile",
"is_primary": true,
"email_status": null,
"last_used_at": null,
"created_at": "2026-03-04T10:29:00Z"
}
]
},
"contact_method": {
"id": "cm_789",
"method_type": "whatsapp_id",
"value": "1234567890",
"channel_id": null,
"display_name": null,
"phone_type": null,
"is_primary": true,
"email_status": null,
"last_used_at": null,
"created_at": "2026-03-04T10:29:00Z"
}
}
}
message.sent
Triggered when an outbound message is sent to the channel provider.
{
"id": "evt_msg_002",
"type": "message.sent",
"event_id": "evt_msg_002",
"created_at": "2026-03-04T10:35:00Z",
"tenant_id": "tenant_abc123",
"data": {
"message": {
"id": "msg_def456",
"conversation_id": "conv_xyz789",
"platform": "whatsapp",
"channel_id": "ch_123",
"contact_id": "ct_456",
"contact_method_id": "cm_789",
"direction": "outbound",
"message_type": "document",
"text": "Here's your invoice",
"attachments": [
{
"id": "att_def456",
"type": "document",
"filename": "invoice_march_2026.pdf",
"content_type": "application/pdf",
"file_size": 184320,
"url": "https://api.sendseven.com/api/v1/attachments/att_def456/download",
"signed_url": "https://storage.googleapis.com/sendseven-attachments/tenant_abc123/att_def456.pdf?X-Goog-Signature=..."
}
],
"status": "sent",
"from_id": "+1987654321",
"to_id": "+1234567890",
"external_id": "wamid.def456",
"meta": {},
"created_at": "2026-03-04T10:35:00Z",
"sent_at": "2026-03-04T10:35:01Z",
"delivered_at": null,
"read_at": null
},
"conversation": {
"id": "conv_xyz789",
"channel_id": "ch_123",
"contact_id": "ct_456",
"status": "open",
"subject": null,
"created_at": "2026-03-04T10:29:00Z"
},
"contact": {
"id": "ct_456",
"name": "John Doe",
"email": "[email protected]",
"phone": "+1234567890",
"contact_methods": [
{
"id": "cm_789",
"method_type": "whatsapp_id",
"value": "1234567890",
"channel_id": null,
"display_name": null,
"phone_type": null,
"is_primary": true,
"email_status": null,
"last_used_at": "2026-03-04T10:35:01Z",
"created_at": "2026-03-04T10:29:00Z"
},
{
"id": "cm_790",
"method_type": "phone",
"value": "+1234567890",
"channel_id": null,
"display_name": null,
"phone_type": "mobile",
"is_primary": true,
"email_status": null,
"last_used_at": null,
"created_at": "2026-03-04T10:29:00Z"
}
]
},
"contact_method": {
"id": "cm_789",
"method_type": "whatsapp_id",
"value": "1234567890",
"channel_id": null,
"display_name": null,
"phone_type": null,
"is_primary": true,
"email_status": null,
"last_used_at": "2026-03-04T10:35:01Z",
"created_at": "2026-03-04T10:29:00Z"
}
}
}
message.delivered
Triggered when delivery is confirmed by the channel.
{
"id": "evt_msg_003",
"type": "message.delivered",
"event_id": "evt_msg_003",
"created_at": "2026-03-04T10:35:05Z",
"tenant_id": "tenant_abc123",
"data": {
"message": {
"id": "msg_def456",
"conversation_id": "conv_xyz789",
"platform": "whatsapp",
"channel_id": "ch_123",
"contact_id": "ct_456",
"contact_method_id": "cm_789",
"direction": "outbound",
"message_type": "text",
"text": "Hi John! Let me look into your order right away.",
"status": "delivered",
"from_id": "+1987654321",
"to_id": "+1234567890",
"external_id": "wamid.def456",
"meta": {},
"created_at": "2026-03-04T10:35:00Z",
"sent_at": "2026-03-04T10:35:01Z",
"delivered_at": "2026-03-04T10:35:05Z",
"read_at": null
},
"conversation": {
"id": "conv_xyz789",
"channel_id": "ch_123",
"contact_id": "ct_456",
"status": "open",
"subject": null,
"created_at": "2026-03-04T10:29:00Z"
},
"contact": {
"id": "ct_456",
"name": "John Doe",
"email": "[email protected]",
"phone": "+1234567890"
}
}
}
message.read
Triggered when a read receipt is received from the channel, confirming the contact has seen the message.
{
"id": "evt_msg_004",
"type": "message.read",
"event_id": "evt_msg_004",
"created_at": "2026-03-04T10:36:12Z",
"tenant_id": "tenant_abc123",
"data": {
"message": {
"id": "msg_def456",
"conversation_id": "conv_xyz789",
"platform": "whatsapp",
"channel_id": "ch_123",
"contact_id": "ct_456",
"contact_method_id": "cm_789",
"direction": "outbound",
"message_type": "text",
"text": "Hi John! Let me look into your order right away.",
"status": "read",
"from_id": "+1987654321",
"to_id": "+1234567890",
"external_id": "wamid.def456",
"meta": {},
"created_at": "2026-03-04T10:35:00Z",
"sent_at": "2026-03-04T10:35:01Z",
"delivered_at": "2026-03-04T10:35:05Z",
"read_at": "2026-03-04T10:36:12Z"
},
"conversation": {
"id": "conv_xyz789",
"channel_id": "ch_123",
"contact_id": "ct_456",
"status": "open",
"subject": null,
"created_at": "2026-03-04T10:29:00Z"
},
"contact": {
"id": "ct_456",
"name": "John Doe",
"email": "[email protected]",
"phone": "+1234567890"
}
}
}
message.failed
Triggered when message delivery fails.
{
"id": "evt_msg_005",
"type": "message.failed",
"event_id": "evt_msg_005",
"created_at": "2026-03-04T10:35:10Z",
"tenant_id": "tenant_abc123",
"data": {
"message": {
"id": "msg_ghi789",
"conversation_id": "conv_xyz789",
"platform": "whatsapp",
"channel_id": "ch_123",
"contact_id": "ct_456",
"contact_method_id": "cm_789",
"direction": "outbound",
"message_type": "text",
"text": "Your order has been shipped!",
"status": "failed",
"from_id": "+1987654321",
"to_id": "+1234567890",
"external_id": null,
"meta": {
"error": "Message undeliverable",
"error_code": "131047"
},
"created_at": "2026-03-04T10:35:08Z",
"sent_at": null,
"delivered_at": null,
"read_at": null
},
"conversation": {
"id": "conv_xyz789",
"channel_id": "ch_123",
"contact_id": "ct_456",
"status": "open",
"subject": null,
"created_at": "2026-03-04T10:29:00Z"
},
"contact": {
"id": "ct_456",
"name": "John Doe",
"email": "[email protected]",
"phone": "+1234567890"
}
}
}
Message Fields Reference
| Field | Type | Description |
|---|---|---|
id | string | Unique message ID |
conversation_id | string | Parent conversation ID |
platform | string | Channel platform (whatsapp, telegram, sms, messenger, instagram, email) |
channel_id | string | Channel configuration ID |
contact_id | string | Contact ID |
contact_method_id | string | null | FK to the contact method used for delivery. The full method object is delivered at the top level as data.contact_method (see Contact Method Fields Reference). |
direction | string | inbound or outbound |
message_type | string | text, image, video, audio, document, sticker, location, template, button, interactive |
text | string | null | Message text content |
status | string | received, sent, delivered, read, failed |
from_id | string | Sender identifier (phone number, platform ID) |
to_id | string | null | Recipient identifier |
external_id | string | null | Provider message ID (e.g., WhatsApp wamid) |
meta | object | Additional metadata (always a JSON object). On inbound messages it carries channel-parsed context (e.g. meta.button for button/list clicks); on outbound it carries the sender-supplied metadata (e.g. flow_run_id, template/interactive details); on message.failed it contains error and error_code. Contents differ by direction and message type. |
contact_info | object | null | Inbound only, deprecated. Raw sender-profile snapshot echoed by the channel provider at receipt time. Absent on outbound. See the caution note below — prefer data.contact_method and data.contact. |
created_at | string | ISO 8601 creation timestamp |
sent_at | string | null | ISO 8601 sent timestamp |
delivered_at | string | null | ISO 8601 delivery confirmation timestamp |
attachments | array | null | File attachments with download URLs (see Attachments below) |
read_at | string | null | ISO 8601 read receipt timestamp |
contact_infoInbound message.received payloads may also carry an undocumented contact_info object — a raw snapshot of the sender's profile echoed by the channel provider at receipt time. Its phone field (WhatsApp only) is deprecated and no longer documented: it is a provider echo, not an authoritative value. Use data.contact_method.value for the identifier of the method actually used, and the data.contact record (phone, contact_methods[]) for the contact's stored identifiers. Do not build new integrations against contact_info.
Attachments
When a message includes file-based attachments (images, videos, audio, documents, stickers), the attachments array is included in the message object and each attachment provides two download URLs. The table below describes this enriched file shape only — non-file attachment types (location, contacts, reactions, button/list replies, flow responses) also live in attachments but use different, type-specific fields (see Message Types Reference):
| Field | Type | Description |
|---|---|---|
id | string | Unique attachment ID |
type | string | Attachment type: image, video, audio, document, sticker, file |
filename | string | Original or generated filename |
content_type | string | MIME type (e.g., image/jpeg, application/pdf) |
file_size | integer | null | File size in bytes |
url | string | Stable API download URL — requires authentication, never expires |
signed_url | string | Pre-signed GCS URL — no authentication required, expires in 24 hours |
url for reliable accessThe url field points to the SendSeven API (/api/v1/attachments/{id}/download) and works as long as the attachment exists. Use this for permanent storage or delayed processing. The signed_url is convenient for immediate one-time downloads without authentication but expires after 24 hours.
Non-file attachments (location, contacts, reactions, button/list replies, flow responses) do appear in the attachments array — each in its own type-specific shape, not the file download shape above (they have no url/signed_url/file_size). For example, a button reply is { "type": "button_reply", "button_id": "...", "button_title": "..." } and a location is { "type": "location", "latitude": ..., "longitude": ..., "name": "...", "address": "..." }. Button, list, and quick-reply clicks are additionally surfaced as a structured meta.button object ({ id, payload, text }) for convenience. See the Message Types Reference for the exact shape of every non-file attachment type.
Fallback behavior: If SendSeven could not download the media from the channel platform (e.g., expired WhatsApp media URL), the raw platform data is included with "source": "platform" added to each attachment object instead of the enriched fields.
Conversation Fields Reference
| Field | Type | Description |
|---|---|---|
id | string | Conversation ID |
channel_id | string | Channel configuration ID |
contact_id | string | null | Contact ID |
assigned_user_id | string | null | User the conversation is assigned to, if any. |
bot_session_id | string | null | Active bot/automation session ID, if the conversation is being handled by a bot. |
status | string | open, closed, pending |
subject | string | null | Conversation subject |
last_customer_message_at | string | null | ISO 8601 timestamp of the most recent inbound customer message. |
last_agent_reply_at | string | null | ISO 8601 timestamp of the most recent agent reply. |
snoozed_until | string | null | ISO 8601 UTC timestamp the conversation is snoozed until. When set and in the future, the conversation is "snoozed" (still status: open) and hidden from the Open tab. null when not snoozed. Present on conversation.updated (snooze); not emitted in the message-event conversation sub-object. See Snooze. |
snooze_reopen_on_message | boolean | null | Whether an incoming customer message auto-reopens the snoozed conversation. null when not snoozed. |
created_at | string | ISO 8601 creation timestamp |
Contact Fields Reference
| Field | Type | Description |
|---|---|---|
id | string | Contact ID |
name | string | null | Contact display name |
email | string | null | Contact email address (denormalized from primary email method) |
phone | string | null | Contact phone number (denormalized from primary phone method) |
contact_methods | array | All of the contact's identifiers (WhatsApp ID, phone, email, Telegram ID, etc.), primary method first. See Contact Method Fields Reference. |
The contact_methods[] array is included on every event that carries a contact object (message.received, message.sent, message.delivered, message.read, message.failed, conversation.created, conversation.assigned, contact.created, contact.updated, link.clicked). The denormalized top-level phone/email fields are retained for backwards compatibility. Technical, internal-only method types (e.g. live-chat visitor cookies) are never included.
Contact Method Fields Reference
The top-level contact_method object (a sibling of message, conversation, and contact) is included on all message events (message.received, message.sent, message.delivered, message.read, message.failed). It identifies the single contact method actually used for that message (the recipient/sender identifier). The message object still carries a contact_method_id string (an FK reference); the full method object lives at the top level. Each entry in contact.contact_methods[] uses the same shape.
| Field | Type | Description |
|---|---|---|
id | string | Contact method ID (matches message.contact_method_id) |
method_type | string | phone, email, whatsapp_id, telegram_id, messenger_id, instagram_id |
value | string | The identifier value (phone number, email, platform ID) |
channel_id | string | null | Channel ID for channel-scoped methods (messenger_id, instagram_id); null for global methods |
display_name | string | null | Display name associated with the method |
phone_type | string | null | For phone methods: mobile, landline, etc. |
is_primary | boolean | Whether this is the primary method of its type |
email_status | string | null | For email methods: validation/deliverability status |
last_used_at | string | null | ISO 8601 timestamp the method was last used |
created_at | string | ISO 8601 creation timestamp |
Conversation Events
conversation.created
Triggered when a new conversation starts.
{
"id": "evt_conv_001",
"type": "conversation.created",
"event_id": "evt_conv_001",
"created_at": "2026-03-04T10:29:00Z",
"tenant_id": "tenant_abc123",
"data": {
"conversation_id": "conv_xyz789",
"contact_id": "ct_456",
"platform": "whatsapp",
"channel_id": "ch_123",
"status": "open",
"assigned_user_id": null,
"created_at": "2026-03-04T10:29:00Z",
"contact": {
"id": "ct_456",
"name": "John Doe",
"email": "[email protected]",
"phone": "+1234567890",
"contact_methods": [
{
"id": "cm_789",
"method_type": "whatsapp_id",
"value": "1234567890",
"channel_id": null,
"display_name": null,
"phone_type": null,
"is_primary": true,
"email_status": null,
"last_used_at": null,
"created_at": "2026-03-04T10:29:00Z"
}
]
}
}
}
conversation.updated
Triggered when a conversation changes without opening or closing — currently when it is snoozed or unsnoozed. The data.change field tells you which transition occurred, and the embedded conversation object carries the current snooze fields. See the Snooze guide for the full feature.
{
"id": "evt_conv_010",
"type": "conversation.updated",
"event_id": "evt_conv_010",
"created_at": "2026-06-22T14:00:00Z",
"tenant_id": "tenant_abc123",
"data": {
"change": "snoozed",
"conversation": {
"id": "conv_xyz789",
"channel_id": "ch_123",
"contact_id": "ct_456",
"status": "open",
"subject": null,
"snoozed_until": "2026-06-23T09:00:00Z",
"snooze_reopen_on_message": true,
"created_at": "2026-06-22T10:29:00Z"
},
"actor": {
"user_id": "user_1a2b3c"
}
}
}
data field | Description |
|---|---|
change | The transition: "snoozed" or "unsnoozed". |
conversation | The updated conversation object (includes snoozed_until and snooze_reopen_on_message). |
actor | The user who triggered the change. Omitted for system-triggered changes such as auto-reopen when a customer replies to a snoozed conversation. |
A snoozed conversation keeps status: "open" — "snoozed" simply means snoozed_until is in the future. When that time passes, the conversation returns to the Open tab automatically and no conversation.updated event is emitted for the lapse (expiry is evaluated at query time). You only receive conversation.updated for explicit snooze/unsnooze actions and for auto-reopen on an inbound message.
conversation.closed
Triggered when a conversation is closed/resolved.
{
"id": "evt_conv_002",
"type": "conversation.closed",
"event_id": "evt_conv_002",
"created_at": "2026-03-04T15:00:00Z",
"tenant_id": "tenant_abc123",
"data": {
"conversation_id": "conv_xyz789",
"contact_id": "ct_456",
"platform": "whatsapp",
"channel_id": "ch_123",
"status": "closed",
"assigned_user_id": "user_1a2b3c",
"closed_by": "user_1a2b3c",
"summary": "Order issue resolved",
"created_at": "2026-03-04T10:29:00Z",
"last_customer_message_at": "2026-03-04T14:30:00Z",
"last_agent_reply_at": "2026-03-04T14:45:00Z"
}
}
conversation.transcript.created
Triggered when a transcript export of a conversation has finished generating and is ready to download. Transcript exports are requested via POST /api/v1/conversations/{conversation_id}/transcript (with a format of pdf or zip); the file is generated asynchronously, so this event fires once the artifact has been rendered and uploaded to storage.
{
"id": "evt_transcript_001",
"type": "conversation.transcript.created",
"event_id": "job_9f8e7d6c",
"created_at": "2026-07-02T12:00:00Z",
"tenant_id": "tenant_abc123",
"data": {
"conversation_id": "conv_xyz789",
"tenant_id": "tenant_abc123",
"contact_id": "ct_456",
"format": "pdf",
"job_id": "job_9f8e7d6c",
"download_url": "https://storage.googleapis.com/sendseven-exports/tenant_abc123/exports/2026-07-02-1200-John-Doe.pdf?X-Goog-Signature=...",
"download_url_expires_at": "2026-07-02T13:00:00Z",
"download_url_expires_in_minutes": 60,
"poll_url": "/api/v1/conversations/conv_xyz789/transcript/job_9f8e7d6c",
"filename": "2026-07-02-1200-John-Doe.pdf"
}
}
data field | Type | Description |
|---|---|---|
conversation_id | string | Conversation the transcript was exported from. |
tenant_id | string | Your tenant ID (also on the envelope). |
contact_id | string | null | Contact on the conversation, if the conversation has one. |
format | string | Export format: pdf or zip. |
job_id | string | The export job ID. Also used as the event event_id. |
download_url | string | Pre-signed, short-lived download URL for the artifact (forces download with a friendly filename). |
download_url_expires_at | string | ISO 8601 UTC timestamp when download_url expires. |
download_url_expires_in_minutes | integer | Lifetime of download_url in minutes. |
poll_url | string | Durable API path to re-fetch a fresh download URL after the signed URL expires — GET /api/v1/conversations/{conversation_id}/transcript/{job_id}. |
filename | string | Suggested download filename. |
download_url is short-livedThe download_url is a pre-signed storage URL that expires (see download_url_expires_at). If your webhook handler processes the event after that window — for example after a retry or a queued job — the URL will no longer work. Use poll_url to fetch a fresh download URL on demand instead of persisting download_url.
Contact Events
contact.created
Triggered when a new contact is added.
{
"id": "evt_contact_001",
"type": "contact.created",
"event_id": "evt_contact_001",
"created_at": "2026-03-04T10:29:00Z",
"tenant_id": "tenant_abc123",
"data": {
"contact_id": "ct_456",
"name": "John Doe",
"email": "[email protected]",
"phone": "+1234567890",
"contact_methods": [
{
"id": "cm_790",
"method_type": "phone",
"value": "+1234567890",
"channel_id": null,
"display_name": null,
"phone_type": "mobile",
"is_primary": true,
"email_status": null,
"last_used_at": null,
"created_at": "2026-03-04T10:29:00Z"
},
{
"id": "cm_791",
"method_type": "email",
"value": "[email protected]",
"channel_id": null,
"display_name": null,
"phone_type": null,
"is_primary": true,
"email_status": "valid",
"last_used_at": null,
"created_at": "2026-03-04T10:29:00Z"
}
],
"created_at": "2026-03-04T10:29:00Z"
}
}
contact.updated
Triggered when contact information changes.
{
"id": "evt_contact_002",
"type": "contact.updated",
"event_id": "evt_contact_002",
"created_at": "2026-03-04T11:15:00Z",
"tenant_id": "tenant_abc123",
"data": {
"contact_id": "ct_456",
"name": "John Doe",
"email": "[email protected]",
"phone": "+1234567890",
"contact_methods": [
{
"id": "cm_790",
"method_type": "phone",
"value": "+1234567890",
"channel_id": null,
"display_name": null,
"phone_type": "mobile",
"is_primary": true,
"email_status": null,
"last_used_at": null,
"created_at": "2026-03-04T10:29:00Z"
},
{
"id": "cm_792",
"method_type": "email",
"value": "[email protected]",
"channel_id": null,
"display_name": null,
"phone_type": null,
"is_primary": true,
"email_status": "valid",
"last_used_at": null,
"created_at": "2026-03-04T11:15:00Z"
}
],
"updated_at": "2026-03-04T11:15:00Z"
}
}
Campaign Events
link.clicked
Triggered when a tracked link in a campaign message is clicked.
{
"id": "evt_link_001",
"type": "link.clicked",
"event_id": "evt_link_001",
"created_at": "2026-03-04T16:00:00Z",
"tenant_id": "tenant_abc123",
"data": {
"url": "https://shop.example.com/spring-sale",
"contact_id": "ct_456",
"campaign_id": "camp_a1b2c3d4",
"contact": {
"id": "ct_456",
"name": "John Doe",
"email": "[email protected]",
"phone": "+1234567890",
"contact_methods": [
{
"id": "cm_790",
"method_type": "phone",
"value": "+1234567890",
"channel_id": null,
"display_name": null,
"phone_type": "mobile",
"is_primary": true,
"email_status": null,
"last_used_at": null,
"created_at": "2026-03-04T10:29:00Z"
}
]
}
}
}
Email Events
Email events have a different payload structure than message events, including email-specific fields like subject, body HTML, and threading information.
email.received
Triggered when an inbound email is received.
{
"id": "evt_email_001",
"type": "email.received",
"event_id": "evt_email_001",
"created_at": "2026-03-04T16:00:00Z",
"tenant_id": "tenant_abc123",
"data": {
"message_id": "msg_email_789",
"conversation_id": "conv_email_123",
"contact_id": "ct_sender",
"platform": "email",
"subject": "Question about my invoice",
"body_html": "<p>Hi, I have a question about my January invoice...</p>",
"from_id": "[email protected]",
"to_id": "[email protected]",
"cc": [],
"email_integration_id": "ei_abc123",
"email_thread_id": "thread_def456",
"created_at": "2026-03-04T16:00:00Z"
}
}
email.sent
Triggered when an outbound email is sent.
{
"id": "evt_email_002",
"type": "email.sent",
"event_id": "evt_email_002",
"created_at": "2026-03-04T16:05:00Z",
"tenant_id": "tenant_abc123",
"data": {
"message_id": "msg_email_790",
"conversation_id": "conv_email_123",
"contact_id": "ct_sender",
"platform": "email",
"subject": "Re: Question about my invoice",
"from_id": "[email protected]",
"to_id": "[email protected]",
"cc": [],
"email_integration_id": "ei_abc123",
"email_thread_id": "thread_def456",
"sent_at": "2026-03-04T16:05:00Z"
}
}
email.delivered
Triggered when email delivery is confirmed.
{
"id": "evt_email_003",
"type": "email.delivered",
"event_id": "evt_email_003",
"created_at": "2026-03-04T16:05:10Z",
"tenant_id": "tenant_abc123",
"data": {
"message_id": "msg_email_790",
"conversation_id": "conv_email_123",
"contact_id": "ct_sender",
"platform": "email",
"recipient": "[email protected]",
"delivered_at": "2026-03-04T16:05:10Z"
}
}
email.bounced
Triggered when an email bounces (hard or soft).
{
"id": "evt_email_004",
"type": "email.bounced",
"event_id": "evt_email_004",
"created_at": "2026-03-04T16:05:15Z",
"tenant_id": "tenant_abc123",
"data": {
"message_id": "msg_email_791",
"conversation_id": "conv_email_124",
"contact_id": "ct_bounced",
"platform": "email",
"bounce_type": "hard",
"recipient": "[email protected]",
"error_message": "550 5.1.1 The email account that you tried to reach does not exist"
}
}
| Field | Description |
|---|---|
bounce_type | hard or soft |
recipient | Email address that bounced |
error_message | Bounce reason from the mail server |
email.opened
Triggered when a recipient opens an email (via tracking pixel).
{
"id": "evt_email_005",
"type": "email.opened",
"event_id": "evt_email_005",
"created_at": "2026-03-04T17:20:00Z",
"tenant_id": "tenant_abc123",
"data": {
"message_id": "msg_email_790",
"conversation_id": "conv_email_123",
"contact_id": "ct_sender",
"platform": "email",
"recipient": "[email protected]",
"opened_at": "2026-03-04T17:20:00Z"
}
}
email.complained
Triggered when a recipient marks the email as spam.
{
"id": "evt_email_006",
"type": "email.complained",
"event_id": "evt_email_006",
"created_at": "2026-03-04T18:00:00Z",
"tenant_id": "tenant_abc123",
"data": {
"message_id": "msg_email_790",
"conversation_id": "conv_email_123",
"contact_id": "ct_sender",
"platform": "email",
"recipient": "[email protected]",
"complained_at": "2026-03-04T18:00:00Z"
}
}
Channel Events
Channel events fire over the lifecycle of a channel — a connected messaging integration such as a WhatsApp number, a Telegram bot, an SMS sender, a Messenger/Instagram page, or an email mailbox. They let you react when an integration is connected, changes state (including provider-driven disconnects), or is removed — without polling GET /api/v1/channels.
These events are especially useful if you onboard channels for your customers via channel connect links: subscribe to channel.created to learn the moment a customer finishes connecting, and to channel.updated to be alerted when one of their channels goes unhealthy so you can prompt them to reconnect.
All three events carry the same channel object in data. The channel object never contains credentials, tokens, or the channel's decrypted configuration — only public/operational fields.
The channel object
| Field | Type | Description |
|---|---|---|
id | string | Channel ID (stable; matches channel_id on message events). |
tenant_id | string | The tenant that owns the channel. |
platform | string | Channel platform: whatsapp, telegram, messenger, instagram, sms, viber, live_chat, browser_push, email. |
name | string | null | User-assigned display name. |
identifier | string | null | The channel's external identifier — phone number, @botname, page ID, or email address, depending on platform. |
is_active | boolean | Whether the channel is currently active (able to send/receive). Flips to false on disconnect. |
is_verified | boolean | Whether the channel has completed verification. |
is_archived | boolean | Whether the channel has been archived (soft-deleted). |
status | string | Convenience roll-up: connected when active and not archived, otherwise disconnected. |
disconnection_reason | string | null | Why the channel was disconnected (e.g. token_invalidated, partner_removed, account_offboarded). null when connected. |
disconnected_at | string | null | ISO 8601 timestamp the channel was last disconnected. |
reconnected_at | string | null | ISO 8601 timestamp the channel was last reconnected. |
created_via_connect_token_id | string | null | The channel connect token used to create this channel, if it was onboarded via a connect link. null for channels created directly in the app. |
created_at | string | ISO 8601 creation timestamp. |
updated_at | string | null | ISO 8601 last-update timestamp. |
channel.created
Triggered when a channel is connected — through the app, the API, an OAuth flow, or a channel connect link.
{
"id": "evt_channel_001",
"type": "channel.created",
"event_id": "ch_123",
"created_at": "2026-03-04T09:00:00Z",
"tenant_id": "tenant_abc123",
"data": {
"channel": {
"id": "ch_123",
"tenant_id": "tenant_abc123",
"platform": "whatsapp",
"name": "Support WhatsApp",
"identifier": "+1987654321",
"is_active": true,
"is_verified": true,
"is_archived": false,
"status": "connected",
"disconnection_reason": null,
"disconnected_at": null,
"reconnected_at": null,
"created_via_connect_token_id": "cct_9a8b7c6d",
"created_at": "2026-03-04T09:00:00Z",
"updated_at": null
}
}
}
channel.updated
Triggered when a channel changes without being created or removed. The top-level data.change field classifies the transition:
change | Meaning |
|---|---|
status_changed | Connection state changed — the channel was disconnected (e.g. token revoked, partner removed) or reconnected. Inspect is_active, status, disconnection_reason, disconnected_at, and reconnected_at. This is the event to watch for channel-health alerting. |
settings_updated | A user edited the channel's name, identifier, verification state, or configuration. |
restored | An archived channel was un-archived (restored). |
Example — a channel goes offline (this is how you find out an integration needs attention):
{
"id": "evt_channel_002",
"type": "channel.updated",
"event_id": "ch_123",
"created_at": "2026-03-04T14:22:00Z",
"tenant_id": "tenant_abc123",
"data": {
"change": "status_changed",
"channel": {
"id": "ch_123",
"tenant_id": "tenant_abc123",
"platform": "whatsapp",
"name": "Support WhatsApp",
"identifier": "+1987654321",
"is_active": false,
"is_verified": true,
"is_archived": false,
"status": "disconnected",
"disconnection_reason": "token_invalidated",
"disconnected_at": "2026-03-04T14:22:00Z",
"reconnected_at": null,
"created_via_connect_token_id": "cct_9a8b7c6d",
"created_at": "2026-03-04T09:00:00Z",
"updated_at": "2026-03-04T14:22:00Z"
}
}
}
status_changedWhen data.change == "status_changed" and channel.status == "disconnected", surface disconnection_reason to the affected customer and point them at a reconnect flow. When the same channel later comes back, you receive another channel.updated with status: "connected" and a populated reconnected_at.
channel.deleted
Triggered when a channel is removed — either archived (the normal in-app "delete", which is a soft delete) or permanently deleted. The payload is a snapshot of the channel as it was at removal time.
{
"id": "evt_channel_003",
"type": "channel.deleted",
"event_id": "ch_123",
"created_at": "2026-03-05T08:00:00Z",
"tenant_id": "tenant_abc123",
"data": {
"channel": {
"id": "ch_123",
"tenant_id": "tenant_abc123",
"platform": "whatsapp",
"name": "Support WhatsApp",
"identifier": "+1987654321",
"is_active": false,
"is_verified": true,
"is_archived": true,
"status": "disconnected",
"disconnection_reason": null,
"disconnected_at": null,
"reconnected_at": null,
"created_via_connect_token_id": "cct_9a8b7c6d",
"created_at": "2026-03-04T09:00:00Z",
"updated_at": "2026-03-05T08:00:00Z"
}
}
}
Like message events, channel events respect a webhook endpoint's channel source filter. If you configure an endpoint to only receive events for selected channels, channel.* events are matched on data.channel.id.
Event Summary Table
| Event | Trigger |
|---|---|
message.received | New inbound message from a contact |
message.sent | Outbound message sent to channel provider |
message.delivered | Delivery confirmed by channel |
message.read | Read receipt received from channel |
message.failed | Message delivery failed |
conversation.created | New conversation started |
conversation.closed | Conversation closed/resolved |
conversation.assigned | Conversation assigned to a user |
conversation.reopened | Closed conversation reopened |
conversation.updated | Conversation snoozed or unsnoozed |
conversation.transcript.created | Transcript export (PDF/ZIP) finished generating and is ready to download |
contact.created | New contact added |
contact.updated | Contact information changed |
contact.deleted | Contact deleted |
contact.subscribed | Contact subscribed to a list |
contact.unsubscribed | Contact unsubscribed from a list |
campaign.message.sent | Campaign message sent to a contact |
campaign.message.delivered | Campaign message delivery confirmed |
campaign.message.read | Campaign message read by the recipient |
campaign.message.failed | Campaign message delivery failed |
campaign.email.sent | Campaign email sent to a contact |
campaign.email.delivered | Campaign email delivery confirmed |
campaign.email.bounced | Campaign email bounced |
campaign.email.opened | Campaign email opened |
campaign.email.complained | Campaign email marked as spam |
channel.created | A channel (messaging integration) was connected |
channel.updated | A channel changed status (disconnected/reconnected), had its settings edited, or was restored |
channel.deleted | A channel was removed (archived or permanently deleted) |
link.clicked | Tracked link clicked |
email.received | Inbound email received |
email.sent | Outbound email sent |
email.delivered | Email delivery confirmed |
email.bounced | Email bounced |
email.opened | Email opened |
email.complained | Email marked as spam |
This table is the complete, authoritative list of events you can subscribe to (mirrors the API's validation list). Detailed data payloads are shown above for the most common events; conversation.assigned, conversation.reopened, contact.deleted, contact.subscribed, contact.unsubscribed, and the campaign.* families all use the same envelope with event-appropriate data. If you need a field-level payload example for one of these that is not shown above, contact dev support so we can document the exact shape rather than have you guess.
Next Steps
- Message Types Reference -- detailed payloads for every message type
- Webhook Setup -- create and manage webhooks
- Signature Verification -- secure your webhook endpoint