Skip to main content

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
}
}
FieldTypeDescription
idstringUnique event ID (for deduplication)
typestringEvent type (e.g., message.received)
event_idstringAlias for id, included for convenience
created_atstringISO 8601 timestamp
tenant_idstringYour tenant ID
dataobjectEvent-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.)

tip

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

FieldTypeDescription
idstringUnique message ID
conversation_idstringParent conversation ID
platformstringChannel platform (whatsapp, telegram, sms, messenger, instagram, email)
channel_idstringChannel configuration ID
contact_idstringContact ID
contact_method_idstring | nullFK 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).
directionstringinbound or outbound
message_typestringtext, image, video, audio, document, sticker, location, template, button, interactive
textstring | nullMessage text content
statusstringreceived, sent, delivered, read, failed
from_idstringSender identifier (phone number, platform ID)
to_idstring | nullRecipient identifier
external_idstring | nullProvider message ID (e.g., WhatsApp wamid)
metaobjectAdditional 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_infoobject | nullInbound 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_atstringISO 8601 creation timestamp
sent_atstring | nullISO 8601 sent timestamp
delivered_atstring | nullISO 8601 delivery confirmation timestamp
attachmentsarray | nullFile attachments with download URLs (see Attachments below)
read_atstring | nullISO 8601 read receipt timestamp
Deprecated: contact_info

Inbound 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):

FieldTypeDescription
idstringUnique attachment ID
typestringAttachment type: image, video, audio, document, sticker, file
filenamestringOriginal or generated filename
content_typestringMIME type (e.g., image/jpeg, application/pdf)
file_sizeinteger | nullFile size in bytes
urlstringStable API download URL — requires authentication, never expires
signed_urlstringPre-signed GCS URL — no authentication required, expires in 24 hours
Use url for reliable access

The 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

FieldTypeDescription
idstringConversation ID
channel_idstringChannel configuration ID
contact_idstring | nullContact ID
assigned_user_idstring | nullUser the conversation is assigned to, if any.
bot_session_idstring | nullActive bot/automation session ID, if the conversation is being handled by a bot.
statusstringopen, closed, pending
subjectstring | nullConversation subject
last_customer_message_atstring | nullISO 8601 timestamp of the most recent inbound customer message.
last_agent_reply_atstring | nullISO 8601 timestamp of the most recent agent reply.
snoozed_untilstring | nullISO 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_messageboolean | nullWhether an incoming customer message auto-reopens the snoozed conversation. null when not snoozed.
created_atstringISO 8601 creation timestamp

Contact Fields Reference

FieldTypeDescription
idstringContact ID
namestring | nullContact display name
emailstring | nullContact email address (denormalized from primary email method)
phonestring | nullContact phone number (denormalized from primary phone method)
contact_methodsarrayAll 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.

FieldTypeDescription
idstringContact method ID (matches message.contact_method_id)
method_typestringphone, email, whatsapp_id, telegram_id, messenger_id, instagram_id
valuestringThe identifier value (phone number, email, platform ID)
channel_idstring | nullChannel ID for channel-scoped methods (messenger_id, instagram_id); null for global methods
display_namestring | nullDisplay name associated with the method
phone_typestring | nullFor phone methods: mobile, landline, etc.
is_primarybooleanWhether this is the primary method of its type
email_statusstring | nullFor email methods: validation/deliverability status
last_used_atstring | nullISO 8601 timestamp the method was last used
created_atstringISO 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 fieldDescription
changeThe transition: "snoozed" or "unsnoozed".
conversationThe updated conversation object (includes snoozed_until and snooze_reopen_on_message).
actorThe user who triggered the change. Omitted for system-triggered changes such as auto-reopen when a customer replies to a snoozed conversation.
Snooze is a derived state

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 fieldTypeDescription
conversation_idstringConversation the transcript was exported from.
tenant_idstringYour tenant ID (also on the envelope).
contact_idstring | nullContact on the conversation, if the conversation has one.
formatstringExport format: pdf or zip.
job_idstringThe export job ID. Also used as the event event_id.
download_urlstringPre-signed, short-lived download URL for the artifact (forces download with a friendly filename).
download_url_expires_atstringISO 8601 UTC timestamp when download_url expires.
download_url_expires_in_minutesintegerLifetime of download_url in minutes.
poll_urlstringDurable API path to re-fetch a fresh download URL after the signed URL expires — GET /api/v1/conversations/{conversation_id}/transcript/{job_id}.
filenamestringSuggested download filename.
download_url is short-lived

The 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"
}
}
FieldDescription
bounce_typehard or soft
recipientEmail address that bounced
error_messageBounce 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

FieldTypeDescription
idstringChannel ID (stable; matches channel_id on message events).
tenant_idstringThe tenant that owns the channel.
platformstringChannel platform: whatsapp, telegram, messenger, instagram, sms, viber, live_chat, browser_push, email.
namestring | nullUser-assigned display name.
identifierstring | nullThe channel's external identifier — phone number, @botname, page ID, or email address, depending on platform.
is_activebooleanWhether the channel is currently active (able to send/receive). Flips to false on disconnect.
is_verifiedbooleanWhether the channel has completed verification.
is_archivedbooleanWhether the channel has been archived (soft-deleted).
statusstringConvenience roll-up: connected when active and not archived, otherwise disconnected.
disconnection_reasonstring | nullWhy the channel was disconnected (e.g. token_invalidated, partner_removed, account_offboarded). null when connected.
disconnected_atstring | nullISO 8601 timestamp the channel was last disconnected.
reconnected_atstring | nullISO 8601 timestamp the channel was last reconnected.
created_via_connect_token_idstring | nullThe 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_atstringISO 8601 creation timestamp.
updated_atstring | nullISO 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:

changeMeaning
status_changedConnection 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_updatedA user edited the channel's name, identifier, verification state, or configuration.
restoredAn 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"
}
}
}
Build channel-health alerts on status_changed

When 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"
}
}
}
Scope channel events to specific channels

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

EventTrigger
message.receivedNew inbound message from a contact
message.sentOutbound message sent to channel provider
message.deliveredDelivery confirmed by channel
message.readRead receipt received from channel
message.failedMessage delivery failed
conversation.createdNew conversation started
conversation.closedConversation closed/resolved
conversation.assignedConversation assigned to a user
conversation.reopenedClosed conversation reopened
conversation.updatedConversation snoozed or unsnoozed
conversation.transcript.createdTranscript export (PDF/ZIP) finished generating and is ready to download
contact.createdNew contact added
contact.updatedContact information changed
contact.deletedContact deleted
contact.subscribedContact subscribed to a list
contact.unsubscribedContact unsubscribed from a list
campaign.message.sentCampaign message sent to a contact
campaign.message.deliveredCampaign message delivery confirmed
campaign.message.readCampaign message read by the recipient
campaign.message.failedCampaign message delivery failed
campaign.email.sentCampaign email sent to a contact
campaign.email.deliveredCampaign email delivery confirmed
campaign.email.bouncedCampaign email bounced
campaign.email.openedCampaign email opened
campaign.email.complainedCampaign email marked as spam
channel.createdA channel (messaging integration) was connected
channel.updatedA channel changed status (disconnected/reconnected), had its settings edited, or was restored
channel.deletedA channel was removed (archived or permanently deleted)
link.clickedTracked link clicked
email.receivedInbound email received
email.sentOutbound email sent
email.deliveredEmail delivery confirmed
email.bouncedEmail bounced
email.openedEmail opened
email.complainedEmail marked as spam
Subscribe to exactly what you need

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