Webhook events
bubbl3 sends 14 event types, plus one conversion.<event key> type per event in the workspace's catalog in Settings, Events (for example conversion.lead.replied), which an endpoint subscribes to by name. A webhook URL added on an event's page is an endpoint subscribed to that event's type. Every delivery is one JSON object with the same envelope, signed as described in the webhooks quick-start. The examples are the bodies a test event sends.
Envelope
| Field | Type | Description |
|---|---|---|
id | string | Event id (evt_...). |
type | "message.received" | |
created_at | string (date-time) | ISO 8601 timestamp. |
workspace_id | string | Workspace id (ws_...). |
data | object |
message.received
A contact sent a message to one of your lines.
data fields
| Field | Type | Required | Description |
|---|---|---|---|
message | object | Yes | |
message.id | string | Yes | Message id (msg_...). |
message.conversation_id | string | Yes | Conversation id (cnv_...). |
message.contact_id | string | Yes | Contact id (ctc_...). |
message.line_id | string or null | Yes | Line id (ln_...). |
message.direction | enum | Yes | One of: outbound, inbound. |
message.channel | enum | Yes | One of: imessage, sms. |
message.to | string or null | Yes | Phone number in E.164 format. |
message.from | string or null | Yes | Phone number in E.164 format. |
message.body | string | Yes | |
message.media | object[] | Yes | |
message.media[].url | string | Yes | |
message.media[].mime | string or null | Yes | |
message.media[].filename | string or null | Yes | |
message.media[].size | integer or null | Yes | From 0 to 9007199254740991. |
message.status | enum | Yes | One of: queued, scheduled, sending, sent, delivered, read, failed, received. |
message.error_code | string or null | Yes | |
message.error_details | object or null | Yes | |
message.scheduled_for | string (date-time) or null | Yes | ISO 8601 timestamp. |
message.sent_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
message.delivered_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
message.read_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
message.external_id | string or null | Yes | |
message.source | enum or null | Yes | One of: api, dashboard, workflow, agent, outreach, mcp. |
message.sent_by_staff | boolean | Yes | |
message.metadata | object | Yes | |
message.is_test | boolean | Yes | |
message.created_at | string (date-time) | Yes | ISO 8601 timestamp. |
message.updated_at | string (date-time) | Yes | ISO 8601 timestamp. |
Example
{
"id": "evt_7Hc2LmQ9xT4bWz1R",
"type": "message.received",
"created_at": "2026-10-09T15:04:05.000Z",
"workspace_id": "ws_7Hc2LmQ9xT4bWz1R",
"data": {
"message": {
"id": "msg_TestMessage000001",
"conversation_id": "cnv_TestConvo00000001",
"contact_id": "ctc_TestContact000001",
"line_id": "ln_TestLine0000000001",
"direction": "inbound",
"channel": "imessage",
"to": "+12125550101",
"from": "+12125550100",
"body": "This is a test event from bubbl3.",
"media": [],
"status": "received",
"error_code": null,
"error_details": null,
"scheduled_for": null,
"sent_at": null,
"delivered_at": null,
"read_at": null,
"external_id": null,
"source": "api",
"sent_by_staff": false,
"metadata": {},
"is_test": true,
"created_at": "2026-01-01T12:00:00.000Z",
"updated_at": "2026-01-01T12:00:00.000Z"
}
}
}
message.status
A message you sent changed status, for example from sent to delivered or read, or failed.
data fields
| Field | Type | Required | Description |
|---|---|---|---|
message | object | Yes | |
message.id | string | Yes | Message id (msg_...). |
message.conversation_id | string | Yes | Conversation id (cnv_...). |
message.contact_id | string | Yes | Contact id (ctc_...). |
message.line_id | string or null | Yes | Line id (ln_...). |
message.direction | enum | Yes | One of: outbound, inbound. |
message.channel | enum | Yes | One of: imessage, sms. |
message.to | string or null | Yes | Phone number in E.164 format. |
message.from | string or null | Yes | Phone number in E.164 format. |
message.body | string | Yes | |
message.media | object[] | Yes | |
message.media[].url | string | Yes | |
message.media[].mime | string or null | Yes | |
message.media[].filename | string or null | Yes | |
message.media[].size | integer or null | Yes | From 0 to 9007199254740991. |
message.status | enum | Yes | One of: queued, scheduled, sending, sent, delivered, read, failed, received. |
message.error_code | string or null | Yes | |
message.error_details | object or null | Yes | |
message.scheduled_for | string (date-time) or null | Yes | ISO 8601 timestamp. |
message.sent_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
message.delivered_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
message.read_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
message.external_id | string or null | Yes | |
message.source | enum or null | Yes | One of: api, dashboard, workflow, agent, outreach, mcp. |
message.sent_by_staff | boolean | Yes | |
message.metadata | object | Yes | |
message.is_test | boolean | Yes | |
message.created_at | string (date-time) | Yes | ISO 8601 timestamp. |
message.updated_at | string (date-time) | Yes | ISO 8601 timestamp. |
previous_status | enum or null | Yes | One of: queued, scheduled, sending, sent, delivered, read, failed, received. |
Example
{
"id": "evt_7Hc2LmQ9xT4bWz1R",
"type": "message.status",
"created_at": "2026-10-09T15:04:05.000Z",
"workspace_id": "ws_7Hc2LmQ9xT4bWz1R",
"data": {
"message": {
"id": "msg_TestMessage000001",
"conversation_id": "cnv_TestConvo00000001",
"contact_id": "ctc_TestContact000001",
"line_id": "ln_TestLine0000000001",
"direction": "outbound",
"channel": "imessage",
"to": "+12125550100",
"from": "+12125550101",
"body": "This is a test event from bubbl3.",
"media": [],
"status": "delivered",
"error_code": null,
"error_details": null,
"scheduled_for": null,
"sent_at": "2026-01-01T12:00:00.000Z",
"delivered_at": "2026-01-01T12:00:00.000Z",
"read_at": null,
"external_id": null,
"source": "api",
"sent_by_staff": false,
"metadata": {},
"is_test": true,
"created_at": "2026-01-01T12:00:00.000Z",
"updated_at": "2026-01-01T12:00:00.000Z"
},
"previous_status": "sent"
}
}
contact.created
A contact was created, by the API, an import or an inbound message.
data fields
| Field | Type | Required | Description |
|---|---|---|---|
contact | object | Yes | |
contact.id | string | Yes | Contact id (ctc_...). |
contact.phone | string | Yes | Phone number in E.164 format. |
contact.email | string or null | Yes | |
contact.first_name | string or null | Yes | |
contact.last_name | string or null | Yes | |
contact.company | string or null | Yes | |
contact.fields | object | Yes | |
contact.labels | string[] | Yes | |
contact.consent_status | enum | Yes | One of: unknown, opted_in, opted_out. |
contact.consent_source | string or null | Yes | |
contact.consent_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.imessage_capable | boolean or null | Yes | |
contact.capability_checked_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.assigned_line_id | string or null | Yes | Line id (ln_...). |
contact.owner_user_id | string (uuid) or null | Yes | |
contact.last_inbound_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.last_outbound_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.first_reply_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.outreach_class | enum | Yes | cold for a contact from an outbound tool; standard otherwise. One of: standard, cold. |
contact.test_contact | boolean | Yes | Marked for live workflow tests: a live test only texts test contacts. |
contact.created_at | string (date-time) | Yes | ISO 8601 timestamp. |
contact.updated_at | string (date-time) | Yes | ISO 8601 timestamp. |
Example
{
"id": "evt_7Hc2LmQ9xT4bWz1R",
"type": "contact.created",
"created_at": "2026-10-09T15:04:05.000Z",
"workspace_id": "ws_7Hc2LmQ9xT4bWz1R",
"data": {
"contact": {
"id": "ctc_TestContact000001",
"phone": "+12125550100",
"email": null,
"first_name": "Test",
"last_name": "Contact",
"company": null,
"fields": {},
"labels": [],
"consent_status": "opted_in",
"consent_source": "test_event",
"consent_at": "2026-01-01T12:00:00.000Z",
"imessage_capable": true,
"capability_checked_at": "2026-01-01T12:00:00.000Z",
"assigned_line_id": "ln_TestLine0000000001",
"owner_user_id": null,
"last_inbound_at": "2026-01-01T12:00:00.000Z",
"last_outbound_at": null,
"first_reply_at": "2026-01-01T12:00:00.000Z",
"outreach_class": "standard",
"test_contact": false,
"created_at": "2026-01-01T12:00:00.000Z",
"updated_at": "2026-01-01T12:00:00.000Z"
}
}
}
contact.opted_out
A contact opted out, by texting a STOP keyword or through the API or dashboard.
data fields
| Field | Type | Required | Description |
|---|---|---|---|
contact | object | Yes | |
contact.id | string | Yes | Contact id (ctc_...). |
contact.phone | string | Yes | Phone number in E.164 format. |
contact.email | string or null | Yes | |
contact.first_name | string or null | Yes | |
contact.last_name | string or null | Yes | |
contact.company | string or null | Yes | |
contact.fields | object | Yes | |
contact.labels | string[] | Yes | |
contact.consent_status | enum | Yes | One of: unknown, opted_in, opted_out. |
contact.consent_source | string or null | Yes | |
contact.consent_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.imessage_capable | boolean or null | Yes | |
contact.capability_checked_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.assigned_line_id | string or null | Yes | Line id (ln_...). |
contact.owner_user_id | string (uuid) or null | Yes | |
contact.last_inbound_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.last_outbound_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.first_reply_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.outreach_class | enum | Yes | cold for a contact from an outbound tool; standard otherwise. One of: standard, cold. |
contact.test_contact | boolean | Yes | Marked for live workflow tests: a live test only texts test contacts. |
contact.created_at | string (date-time) | Yes | ISO 8601 timestamp. |
contact.updated_at | string (date-time) | Yes | ISO 8601 timestamp. |
keyword | string or null | Yes | The STOP keyword, when the contact texted one. |
source | string | Yes |
Example
{
"id": "evt_7Hc2LmQ9xT4bWz1R",
"type": "contact.opted_out",
"created_at": "2026-10-09T15:04:05.000Z",
"workspace_id": "ws_7Hc2LmQ9xT4bWz1R",
"data": {
"contact": {
"id": "ctc_TestContact000001",
"phone": "+12125550100",
"email": null,
"first_name": "Test",
"last_name": "Contact",
"company": null,
"fields": {},
"labels": [],
"consent_status": "opted_out",
"consent_source": "test_event",
"consent_at": "2026-01-01T12:00:00.000Z",
"imessage_capable": true,
"capability_checked_at": "2026-01-01T12:00:00.000Z",
"assigned_line_id": "ln_TestLine0000000001",
"owner_user_id": null,
"last_inbound_at": "2026-01-01T12:00:00.000Z",
"last_outbound_at": null,
"first_reply_at": "2026-01-01T12:00:00.000Z",
"outreach_class": "standard",
"test_contact": false,
"created_at": "2026-01-01T12:00:00.000Z",
"updated_at": "2026-01-01T12:00:00.000Z"
},
"keyword": "STOP",
"source": "inbound_keyword"
}
}
contact.opted_in
A contact opted back in.
data fields
| Field | Type | Required | Description |
|---|---|---|---|
contact | object | Yes | |
contact.id | string | Yes | Contact id (ctc_...). |
contact.phone | string | Yes | Phone number in E.164 format. |
contact.email | string or null | Yes | |
contact.first_name | string or null | Yes | |
contact.last_name | string or null | Yes | |
contact.company | string or null | Yes | |
contact.fields | object | Yes | |
contact.labels | string[] | Yes | |
contact.consent_status | enum | Yes | One of: unknown, opted_in, opted_out. |
contact.consent_source | string or null | Yes | |
contact.consent_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.imessage_capable | boolean or null | Yes | |
contact.capability_checked_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.assigned_line_id | string or null | Yes | Line id (ln_...). |
contact.owner_user_id | string (uuid) or null | Yes | |
contact.last_inbound_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.last_outbound_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.first_reply_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.outreach_class | enum | Yes | cold for a contact from an outbound tool; standard otherwise. One of: standard, cold. |
contact.test_contact | boolean | Yes | Marked for live workflow tests: a live test only texts test contacts. |
contact.created_at | string (date-time) | Yes | ISO 8601 timestamp. |
contact.updated_at | string (date-time) | Yes | ISO 8601 timestamp. |
keyword | string or null | Yes | |
source | string | Yes |
Example
{
"id": "evt_7Hc2LmQ9xT4bWz1R",
"type": "contact.opted_in",
"created_at": "2026-10-09T15:04:05.000Z",
"workspace_id": "ws_7Hc2LmQ9xT4bWz1R",
"data": {
"contact": {
"id": "ctc_TestContact000001",
"phone": "+12125550100",
"email": null,
"first_name": "Test",
"last_name": "Contact",
"company": null,
"fields": {},
"labels": [],
"consent_status": "opted_in",
"consent_source": "test_event",
"consent_at": "2026-01-01T12:00:00.000Z",
"imessage_capable": true,
"capability_checked_at": "2026-01-01T12:00:00.000Z",
"assigned_line_id": "ln_TestLine0000000001",
"owner_user_id": null,
"last_inbound_at": "2026-01-01T12:00:00.000Z",
"last_outbound_at": null,
"first_reply_at": "2026-01-01T12:00:00.000Z",
"outreach_class": "standard",
"test_contact": false,
"created_at": "2026-01-01T12:00:00.000Z",
"updated_at": "2026-01-01T12:00:00.000Z"
},
"keyword": "START",
"source": "inbound_keyword"
}
}
conversation.assigned
A conversation was assigned to a person or team, or unassigned.
data fields
| Field | Type | Required | Description |
|---|---|---|---|
conversation | object | Yes | |
conversation.id | string | Yes | Conversation id (cnv_...). |
conversation.contact | object | Yes | |
conversation.contact.id | string | Yes | Contact id (ctc_...). |
conversation.contact.phone | string | Yes | Phone number in E.164 format. |
conversation.contact.first_name | string or null | Yes | |
conversation.contact.last_name | string or null | Yes | |
conversation.contact.company | string or null | Yes | |
conversation.line_id | string or null | Yes | Line id (ln_...). |
conversation.status | enum | Yes | One of: open, snoozed, closed. |
conversation.assignee_user_id | string (uuid) or null | Yes | |
conversation.team_id | string (uuid) or null | Yes | |
conversation.labels | string[] | Yes | |
conversation.last_message_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
conversation.last_message | object or null | Yes | |
conversation.last_message.id | string | Yes | Message id (msg_...). |
conversation.last_message.direction | enum | Yes | One of: outbound, inbound. |
conversation.last_message.preview | string | Yes | Up to 200 characters. |
conversation.unread_count | integer | Yes | From 0 to 9007199254740991. |
conversation.handled_by | string | Yes | |
conversation.snoozed_until | string (date-time) or null | Yes | ISO 8601 timestamp. |
conversation.created_at | string (date-time) | Yes | ISO 8601 timestamp. |
conversation.updated_at | string (date-time) | Yes | ISO 8601 timestamp. |
assignee_user_id | string (uuid) or null | Yes | |
previous_assignee_user_id | string (uuid) or null | Yes | |
team_id | string (uuid) or null | Yes |
Example
{
"id": "evt_7Hc2LmQ9xT4bWz1R",
"type": "conversation.assigned",
"created_at": "2026-10-09T15:04:05.000Z",
"workspace_id": "ws_7Hc2LmQ9xT4bWz1R",
"data": {
"conversation": {
"id": "cnv_TestConvo00000001",
"contact": {
"id": "ctc_TestContact000001",
"phone": "+12125550100",
"first_name": "Test",
"last_name": "Contact",
"company": null
},
"line_id": "ln_TestLine0000000001",
"status": "open",
"assignee_user_id": "00000000-0000-4000-8000-000000000001",
"team_id": null,
"labels": [],
"last_message_at": "2026-01-01T12:00:00.000Z",
"last_message": {
"id": "msg_TestMessage000001",
"direction": "inbound",
"preview": "This is a test event from bubbl3."
},
"unread_count": 1,
"handled_by": "human",
"snoozed_until": null,
"created_at": "2026-01-01T12:00:00.000Z",
"updated_at": "2026-01-01T12:00:00.000Z"
},
"assignee_user_id": "00000000-0000-4000-8000-000000000001",
"previous_assignee_user_id": null,
"team_id": null
}
}
line.state_changed
A line moved between states, such as warming, active, paused or retired.
data fields
| Field | Type | Required | Description |
|---|---|---|---|
line | object | Yes | |
line.id | string | Yes | Line id (ln_...). |
line.phone | string | Yes | Phone number in E.164 format. |
line.display_name | string or null | Yes | |
line.contact_card | object | Yes | |
line.contact_card.name | string | No | Up to 100 characters. |
line.contact_card.image_url | string | No | Up to 2048 characters. |
line.pool_id | string or null | Yes | Pool id (pool_...). |
line.state | enum | Yes | One of: provisioning, ready, warming, active, throttled, paused, quarantined, retired. |
line.state_reason | string or null | Yes | |
line.state_changed_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
line.health_score | integer | Yes | From 0 to 100. |
line.warmup_day | integer | Yes | From 0 to 9007199254740991. |
line.daily_new_cap | integer | Yes | From 0 to 9007199254740991. |
line.is_sandbox | boolean | Yes | |
line.usage_today | object | Yes | |
line.usage_today.day | string (date) | Yes | Usage day in America/New_York, rolling over at 3 AM. |
line.usage_today.new_contacts | integer | Yes | From 0 to 9007199254740991. |
line.usage_today.new_contacts_cap | integer | Yes | From 0 to 9007199254740991. |
line.usage_today.sends | integer | Yes | From 0 to 9007199254740991. |
line.usage_today.sends_cap | integer | Yes | From 0 to 9007199254740991. |
line.created_at | string (date-time) | Yes | ISO 8601 timestamp. |
from | enum | Yes | One of: provisioning, ready, warming, active, throttled, paused, quarantined, retired. |
to | enum | Yes | One of: provisioning, ready, warming, active, throttled, paused, quarantined, retired. |
reason | string | Yes |
Example
{
"id": "evt_7Hc2LmQ9xT4bWz1R",
"type": "line.state_changed",
"created_at": "2026-10-09T15:04:05.000Z",
"workspace_id": "ws_7Hc2LmQ9xT4bWz1R",
"data": {
"line": {
"id": "ln_TestLine0000000001",
"phone": "+12125550101",
"display_name": "Test line",
"contact_card": {},
"pool_id": "pool_TestPool00000001",
"state": "active",
"state_reason": null,
"state_changed_at": "2026-01-01T12:00:00.000Z",
"health_score": 92,
"warmup_day": 21,
"daily_new_cap": 50,
"is_sandbox": false,
"usage_today": {
"day": "2026-01-01",
"new_contacts": 0,
"new_contacts_cap": 50,
"sends": 0,
"sends_cap": 300
},
"created_at": "2026-01-01T12:00:00.000Z"
},
"from": "active",
"to": "throttled",
"reason": "low_reply_ratio"
}
}
line.degraded
A line’s health fell below the healthy range; bubbl3 sends less through it.
data fields
| Field | Type | Required | Description |
|---|---|---|---|
line | object | Yes | |
line.id | string | Yes | Line id (ln_...). |
line.phone | string | Yes | Phone number in E.164 format. |
line.display_name | string or null | Yes | |
line.contact_card | object | Yes | |
line.contact_card.name | string | No | Up to 100 characters. |
line.contact_card.image_url | string | No | Up to 2048 characters. |
line.pool_id | string or null | Yes | Pool id (pool_...). |
line.state | enum | Yes | One of: provisioning, ready, warming, active, throttled, paused, quarantined, retired. |
line.state_reason | string or null | Yes | |
line.state_changed_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
line.health_score | integer | Yes | From 0 to 100. |
line.warmup_day | integer | Yes | From 0 to 9007199254740991. |
line.daily_new_cap | integer | Yes | From 0 to 9007199254740991. |
line.is_sandbox | boolean | Yes | |
line.usage_today | object | Yes | |
line.usage_today.day | string (date) | Yes | Usage day in America/New_York, rolling over at 3 AM. |
line.usage_today.new_contacts | integer | Yes | From 0 to 9007199254740991. |
line.usage_today.new_contacts_cap | integer | Yes | From 0 to 9007199254740991. |
line.usage_today.sends | integer | Yes | From 0 to 9007199254740991. |
line.usage_today.sends_cap | integer | Yes | From 0 to 9007199254740991. |
line.created_at | string (date-time) | Yes | ISO 8601 timestamp. |
reason | string | Yes | |
health_score | integer | Yes | From 0 to 100. |
Example
{
"id": "evt_7Hc2LmQ9xT4bWz1R",
"type": "line.degraded",
"created_at": "2026-10-09T15:04:05.000Z",
"workspace_id": "ws_7Hc2LmQ9xT4bWz1R",
"data": {
"line": {
"id": "ln_TestLine0000000001",
"phone": "+12125550101",
"display_name": "Test line",
"contact_card": {},
"pool_id": "pool_TestPool00000001",
"state": "active",
"state_reason": null,
"state_changed_at": "2026-01-01T12:00:00.000Z",
"health_score": 55,
"warmup_day": 21,
"daily_new_cap": 50,
"is_sandbox": false,
"usage_today": {
"day": "2026-01-01",
"new_contacts": 0,
"new_contacts_cap": 50,
"sends": 0,
"sends_cap": 300
},
"created_at": "2026-01-01T12:00:00.000Z"
},
"reason": "health_score",
"health_score": 55
}
}
line.recovered
A degraded line is healthy again.
data fields
| Field | Type | Required | Description |
|---|---|---|---|
line | object | Yes | |
line.id | string | Yes | Line id (ln_...). |
line.phone | string | Yes | Phone number in E.164 format. |
line.display_name | string or null | Yes | |
line.contact_card | object | Yes | |
line.contact_card.name | string | No | Up to 100 characters. |
line.contact_card.image_url | string | No | Up to 2048 characters. |
line.pool_id | string or null | Yes | Pool id (pool_...). |
line.state | enum | Yes | One of: provisioning, ready, warming, active, throttled, paused, quarantined, retired. |
line.state_reason | string or null | Yes | |
line.state_changed_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
line.health_score | integer | Yes | From 0 to 100. |
line.warmup_day | integer | Yes | From 0 to 9007199254740991. |
line.daily_new_cap | integer | Yes | From 0 to 9007199254740991. |
line.is_sandbox | boolean | Yes | |
line.usage_today | object | Yes | |
line.usage_today.day | string (date) | Yes | Usage day in America/New_York, rolling over at 3 AM. |
line.usage_today.new_contacts | integer | Yes | From 0 to 9007199254740991. |
line.usage_today.new_contacts_cap | integer | Yes | From 0 to 9007199254740991. |
line.usage_today.sends | integer | Yes | From 0 to 9007199254740991. |
line.usage_today.sends_cap | integer | Yes | From 0 to 9007199254740991. |
line.created_at | string (date-time) | Yes | ISO 8601 timestamp. |
health_score | integer | Yes | From 0 to 100. |
Example
{
"id": "evt_7Hc2LmQ9xT4bWz1R",
"type": "line.recovered",
"created_at": "2026-10-09T15:04:05.000Z",
"workspace_id": "ws_7Hc2LmQ9xT4bWz1R",
"data": {
"line": {
"id": "ln_TestLine0000000001",
"phone": "+12125550101",
"display_name": "Test line",
"contact_card": {},
"pool_id": "pool_TestPool00000001",
"state": "active",
"state_reason": null,
"state_changed_at": "2026-01-01T12:00:00.000Z",
"health_score": 92,
"warmup_day": 21,
"daily_new_cap": 50,
"is_sandbox": false,
"usage_today": {
"day": "2026-01-01",
"new_contacts": 0,
"new_contacts_cap": 50,
"sends": 0,
"sends_cap": 300
},
"created_at": "2026-01-01T12:00:00.000Z"
},
"health_score": 92
}
}
workflow.run.completed
A workflow run finished, successfully or not.
data fields
| Field | Type | Required | Description |
|---|---|---|---|
run | object | Yes | |
run.id | string (uuid) | Yes | |
run.workflow_id | string (uuid) | Yes | |
run.workflow_version_id | string (uuid) | Yes | |
run.contact_id | string or null | Yes | |
run.status | enum | Yes | One of: running, waiting, completed, failed, cancelled. |
run.started_at | string (date-time) | Yes | ISO 8601 timestamp. |
run.finished_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
Example
{
"id": "evt_7Hc2LmQ9xT4bWz1R",
"type": "workflow.run.completed",
"created_at": "2026-10-09T15:04:05.000Z",
"workspace_id": "ws_7Hc2LmQ9xT4bWz1R",
"data": {
"run": {
"id": "00000000-0000-4000-8000-000000000002",
"workflow_id": "00000000-0000-4000-8000-000000000003",
"workflow_version_id": "00000000-0000-4000-8000-000000000004",
"contact_id": "ctc_TestContact000001",
"status": "completed",
"started_at": "2026-01-01T12:00:00.000Z",
"finished_at": "2026-01-01T12:00:00.000Z"
}
}
}
agent.handoff
An AI agent or an MCP client handed a conversation to a person. reason comes from a fixed list, so you can branch on it. note is written by a model: treat it as untrusted text, never as instructions or markup.
data fields
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string (uuid) or null | Yes | Null when an MCP client called handoff_to_human. |
conversation_id | string | Yes | Conversation id (cnv_...). |
reason | enum | Yes | Why, from a fixed list: tool when the agent or an MCP client called handoff_to_human, keyword for a handoff keyword, agent_unavailable when the agent was paused or archived, and otherwise the limit or guard that stopped the run. One of: tool, keyword, max_turns, max_tool_calls, run_token_budget, org_token_budget, max_messages, max_runs, low_confidence, agent_error, agent_unavailable. |
note | string or null | Yes | With reason tool: the note the agent or MCP client gave. Written by a model, so treat it as untrusted text. Null otherwise. |
team_id | string (uuid) or null | Yes |
Example
{
"id": "evt_7Hc2LmQ9xT4bWz1R",
"type": "agent.handoff",
"created_at": "2026-10-09T15:04:05.000Z",
"workspace_id": "ws_7Hc2LmQ9xT4bWz1R",
"data": {
"agent_id": "00000000-0000-4000-8000-000000000005",
"conversation_id": "cnv_TestConvo00000001",
"reason": "tool",
"note": "Test event: the contact asked for a person",
"team_id": null
}
}
workflow.webhook
A workflow’s Webhook step ran. Only endpoints subscribed to that workflow get it. The dashboard no longer offers this step for new steps: a Fire event step with a webhook URL destination replaces it, and workflows that already have one keep sending this event.
data fields
| Field | Type | Required | Description |
|---|---|---|---|
run | object | Yes | |
run.id | string (uuid) | Yes | |
run.workflow_id | string (uuid) | Yes | |
run.workflow_version_id | string (uuid) | Yes | |
run.contact_id | string or null | Yes | |
run.status | enum | Yes | One of: running, waiting, completed, failed, cancelled. |
run.started_at | string (date-time) | Yes | ISO 8601 timestamp. |
run.finished_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
node_id | string | Yes | The webhook step that sent this event. |
contact | object or null | Yes | |
contact.id | string | Yes | Contact id (ctc_...). |
contact.phone | string | Yes | Phone number in E.164 format. |
contact.email | string or null | Yes | |
contact.first_name | string or null | Yes | |
contact.last_name | string or null | Yes | |
contact.company | string or null | Yes | |
contact.fields | object | Yes | |
contact.labels | string[] | Yes | |
contact.consent_status | enum | Yes | One of: unknown, opted_in, opted_out. |
contact.consent_source | string or null | Yes | |
contact.consent_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.imessage_capable | boolean or null | Yes | |
contact.capability_checked_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.assigned_line_id | string or null | Yes | Line id (ln_...). |
contact.owner_user_id | string (uuid) or null | Yes | |
contact.last_inbound_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.last_outbound_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.first_reply_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.outreach_class | enum | Yes | cold for a contact from an outbound tool; standard otherwise. One of: standard, cold. |
contact.test_contact | boolean | Yes | Marked for live workflow tests: a live test only texts test contacts. |
contact.created_at | string (date-time) | Yes | ISO 8601 timestamp. |
contact.updated_at | string (date-time) | Yes | ISO 8601 timestamp. |
data | object | Yes | Values set on the step. |
test | true | No | Present when the run is a workflow's live test. Absent on real runs. |
Example
{
"id": "evt_7Hc2LmQ9xT4bWz1R",
"type": "workflow.webhook",
"created_at": "2026-10-09T15:04:05.000Z",
"workspace_id": "ws_7Hc2LmQ9xT4bWz1R",
"data": {
"run": {
"id": "00000000-0000-4000-8000-000000000002",
"workflow_id": "00000000-0000-4000-8000-000000000003",
"workflow_version_id": "00000000-0000-4000-8000-000000000004",
"contact_id": "ctc_TestContact000001",
"status": "running",
"started_at": "2026-01-01T12:00:00.000Z",
"finished_at": null
},
"node_id": "notify",
"contact": {
"id": "ctc_TestContact000001",
"phone": "+12125550100",
"email": null,
"first_name": "Test",
"last_name": "Contact",
"company": null,
"fields": {},
"labels": [],
"consent_status": "opted_in",
"consent_source": "test_event",
"consent_at": "2026-01-01T12:00:00.000Z",
"imessage_capable": true,
"capability_checked_at": "2026-01-01T12:00:00.000Z",
"assigned_line_id": "ln_TestLine0000000001",
"owner_user_id": null,
"last_inbound_at": "2026-01-01T12:00:00.000Z",
"last_outbound_at": null,
"first_reply_at": "2026-01-01T12:00:00.000Z",
"outreach_class": "standard",
"test_contact": false,
"created_at": "2026-01-01T12:00:00.000Z",
"updated_at": "2026-01-01T12:00:00.000Z"
},
"data": {
"stage": "test"
}
}
}
lead.received
An inbound source or a connected CRM created or merged a contact. Sent once per lead: a repeated post of the same lead sends nothing.
data fields
| Field | Type | Required | Description |
|---|---|---|---|
contact | object | Yes | |
contact.id | string | Yes | Contact id (ctc_...). |
contact.phone | string | Yes | Phone number in E.164 format. |
contact.email | string or null | Yes | |
contact.first_name | string or null | Yes | |
contact.last_name | string or null | Yes | |
contact.company | string or null | Yes | |
contact.fields | object | Yes | |
contact.labels | string[] | Yes | |
contact.consent_status | enum | Yes | One of: unknown, opted_in, opted_out. |
contact.consent_source | string or null | Yes | |
contact.consent_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.imessage_capable | boolean or null | Yes | |
contact.capability_checked_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.assigned_line_id | string or null | Yes | Line id (ln_...). |
contact.owner_user_id | string (uuid) or null | Yes | |
contact.last_inbound_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.last_outbound_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.first_reply_at | string (date-time) or null | Yes | ISO 8601 timestamp. |
contact.outreach_class | enum | Yes | cold for a contact from an outbound tool; standard otherwise. One of: standard, cold. |
contact.test_contact | boolean | Yes | Marked for live workflow tests: a live test only texts test contacts. |
contact.created_at | string (date-time) | Yes | ISO 8601 timestamp. |
contact.updated_at | string (date-time) | Yes | ISO 8601 timestamp. |
outcome | enum | Yes | One of: created, merged. |
source | object | Yes | |
source.kind | enum | Yes | One of: inbound_source, integration. |
source.id | string | Yes | The inbound source (src_...) or connection (int_...). |
source.name | string or null | Yes | |
source.provider | string or null | Yes | The provider, for connections and Meta sources. |
lead_event_id | string (uuid) or null | Yes | The intake lead event, for inbound sources. |
Example
{
"id": "evt_7Hc2LmQ9xT4bWz1R",
"type": "lead.received",
"created_at": "2026-10-09T15:04:05.000Z",
"workspace_id": "ws_7Hc2LmQ9xT4bWz1R",
"data": {
"contact": {
"id": "ctc_TestContact000001",
"phone": "+12125550100",
"email": null,
"first_name": "Test",
"last_name": "Contact",
"company": null,
"fields": {},
"labels": [],
"consent_status": "opted_in",
"consent_source": "test_event",
"consent_at": "2026-01-01T12:00:00.000Z",
"imessage_capable": true,
"capability_checked_at": "2026-01-01T12:00:00.000Z",
"assigned_line_id": "ln_TestLine0000000001",
"owner_user_id": null,
"last_inbound_at": "2026-01-01T12:00:00.000Z",
"last_outbound_at": null,
"first_reply_at": "2026-01-01T12:00:00.000Z",
"outreach_class": "standard",
"test_contact": false,
"created_at": "2026-01-01T12:00:00.000Z",
"updated_at": "2026-01-01T12:00:00.000Z"
},
"outcome": "created",
"source": {
"kind": "inbound_source",
"id": "src_TestSource00000001",
"name": "Test form",
"provider": null
},
"lead_event_id": "00000000-0000-4000-8000-000000000006"
}
}
integration.connection.status
An integration connection changed status: connected, needs_attention (with the reason) or disconnected.
data fields
| Field | Type | Required | Description |
|---|---|---|---|
connection | object | Yes | |
connection.id | string | Yes | Integration connection id (int_...). |
connection.provider | string | Yes | |
connection.external_account_name | string or null | Yes | |
status | enum | Yes | One of: connected, needs_attention, disconnected. |
previous_status | enum or null | Yes | One of: connected, needs_attention, disconnected. |
reason | string or null | Yes | What needs attention, when it does. |
Example
{
"id": "evt_7Hc2LmQ9xT4bWz1R",
"type": "integration.connection.status",
"created_at": "2026-10-09T15:04:05.000Z",
"workspace_id": "ws_7Hc2LmQ9xT4bWz1R",
"data": {
"connection": {
"id": "int_TestConnection0001",
"provider": "hubspot",
"external_account_name": "Test CRM"
},
"status": "needs_attention",
"previous_status": "connected",
"reason": "Test event: the CRM refused bubbl3’s token"
}
}