Webhook events
bubbl3 sends 12 event types. 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.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",
"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.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",
"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.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",
"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.
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.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. |
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",
"created_at": "2026-01-01T12:00:00.000Z",
"updated_at": "2026-01-01T12:00:00.000Z"
},
"data": {
"stage": "test"
}
}
}