Skip to main content

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​

FieldTypeDescription
idstringEvent id (evt_...).
type"message.received"
created_atstring (date-time)ISO 8601 timestamp.
workspace_idstringWorkspace id (ws_...).
dataobject

message.received​

A contact sent a message to one of your lines.

data fields

FieldTypeRequiredDescription
messageobjectYes
message.idstringYesMessage id (msg_...).
message.conversation_idstringYesConversation id (cnv_...).
message.contact_idstringYesContact id (ctc_...).
message.line_idstring or nullYesLine id (ln_...).
message.directionenumYesOne of: outbound, inbound.
message.channelenumYesOne of: imessage, sms.
message.tostring or nullYesPhone number in E.164 format.
message.fromstring or nullYesPhone number in E.164 format.
message.bodystringYes
message.mediaobject[]Yes
message.media[].urlstringYes
message.media[].mimestring or nullYes
message.media[].filenamestring or nullYes
message.media[].sizeinteger or nullYesFrom 0 to 9007199254740991.
message.statusenumYesOne of: queued, scheduled, sending, sent, delivered, read, failed, received.
message.error_codestring or nullYes
message.error_detailsobject or nullYes
message.scheduled_forstring (date-time) or nullYesISO 8601 timestamp.
message.sent_atstring (date-time) or nullYesISO 8601 timestamp.
message.delivered_atstring (date-time) or nullYesISO 8601 timestamp.
message.read_atstring (date-time) or nullYesISO 8601 timestamp.
message.external_idstring or nullYes
message.sourceenum or nullYesOne of: api, dashboard, workflow, agent, outreach, mcp.
message.sent_by_staffbooleanYes
message.metadataobjectYes
message.is_testbooleanYes
message.created_atstring (date-time)YesISO 8601 timestamp.
message.updated_atstring (date-time)YesISO 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

FieldTypeRequiredDescription
messageobjectYes
message.idstringYesMessage id (msg_...).
message.conversation_idstringYesConversation id (cnv_...).
message.contact_idstringYesContact id (ctc_...).
message.line_idstring or nullYesLine id (ln_...).
message.directionenumYesOne of: outbound, inbound.
message.channelenumYesOne of: imessage, sms.
message.tostring or nullYesPhone number in E.164 format.
message.fromstring or nullYesPhone number in E.164 format.
message.bodystringYes
message.mediaobject[]Yes
message.media[].urlstringYes
message.media[].mimestring or nullYes
message.media[].filenamestring or nullYes
message.media[].sizeinteger or nullYesFrom 0 to 9007199254740991.
message.statusenumYesOne of: queued, scheduled, sending, sent, delivered, read, failed, received.
message.error_codestring or nullYes
message.error_detailsobject or nullYes
message.scheduled_forstring (date-time) or nullYesISO 8601 timestamp.
message.sent_atstring (date-time) or nullYesISO 8601 timestamp.
message.delivered_atstring (date-time) or nullYesISO 8601 timestamp.
message.read_atstring (date-time) or nullYesISO 8601 timestamp.
message.external_idstring or nullYes
message.sourceenum or nullYesOne of: api, dashboard, workflow, agent, outreach, mcp.
message.sent_by_staffbooleanYes
message.metadataobjectYes
message.is_testbooleanYes
message.created_atstring (date-time)YesISO 8601 timestamp.
message.updated_atstring (date-time)YesISO 8601 timestamp.
previous_statusenum or nullYesOne 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

FieldTypeRequiredDescription
contactobjectYes
contact.idstringYesContact id (ctc_...).
contact.phonestringYesPhone number in E.164 format.
contact.emailstring or nullYes
contact.first_namestring or nullYes
contact.last_namestring or nullYes
contact.companystring or nullYes
contact.fieldsobjectYes
contact.labelsstring[]Yes
contact.consent_statusenumYesOne of: unknown, opted_in, opted_out.
contact.consent_sourcestring or nullYes
contact.consent_atstring (date-time) or nullYesISO 8601 timestamp.
contact.imessage_capableboolean or nullYes
contact.capability_checked_atstring (date-time) or nullYesISO 8601 timestamp.
contact.assigned_line_idstring or nullYesLine id (ln_...).
contact.owner_user_idstring (uuid) or nullYes
contact.last_inbound_atstring (date-time) or nullYesISO 8601 timestamp.
contact.last_outbound_atstring (date-time) or nullYesISO 8601 timestamp.
contact.first_reply_atstring (date-time) or nullYesISO 8601 timestamp.
contact.created_atstring (date-time)YesISO 8601 timestamp.
contact.updated_atstring (date-time)YesISO 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

FieldTypeRequiredDescription
contactobjectYes
contact.idstringYesContact id (ctc_...).
contact.phonestringYesPhone number in E.164 format.
contact.emailstring or nullYes
contact.first_namestring or nullYes
contact.last_namestring or nullYes
contact.companystring or nullYes
contact.fieldsobjectYes
contact.labelsstring[]Yes
contact.consent_statusenumYesOne of: unknown, opted_in, opted_out.
contact.consent_sourcestring or nullYes
contact.consent_atstring (date-time) or nullYesISO 8601 timestamp.
contact.imessage_capableboolean or nullYes
contact.capability_checked_atstring (date-time) or nullYesISO 8601 timestamp.
contact.assigned_line_idstring or nullYesLine id (ln_...).
contact.owner_user_idstring (uuid) or nullYes
contact.last_inbound_atstring (date-time) or nullYesISO 8601 timestamp.
contact.last_outbound_atstring (date-time) or nullYesISO 8601 timestamp.
contact.first_reply_atstring (date-time) or nullYesISO 8601 timestamp.
contact.created_atstring (date-time)YesISO 8601 timestamp.
contact.updated_atstring (date-time)YesISO 8601 timestamp.
keywordstring or nullYesThe STOP keyword, when the contact texted one.
sourcestringYes

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

FieldTypeRequiredDescription
contactobjectYes
contact.idstringYesContact id (ctc_...).
contact.phonestringYesPhone number in E.164 format.
contact.emailstring or nullYes
contact.first_namestring or nullYes
contact.last_namestring or nullYes
contact.companystring or nullYes
contact.fieldsobjectYes
contact.labelsstring[]Yes
contact.consent_statusenumYesOne of: unknown, opted_in, opted_out.
contact.consent_sourcestring or nullYes
contact.consent_atstring (date-time) or nullYesISO 8601 timestamp.
contact.imessage_capableboolean or nullYes
contact.capability_checked_atstring (date-time) or nullYesISO 8601 timestamp.
contact.assigned_line_idstring or nullYesLine id (ln_...).
contact.owner_user_idstring (uuid) or nullYes
contact.last_inbound_atstring (date-time) or nullYesISO 8601 timestamp.
contact.last_outbound_atstring (date-time) or nullYesISO 8601 timestamp.
contact.first_reply_atstring (date-time) or nullYesISO 8601 timestamp.
contact.created_atstring (date-time)YesISO 8601 timestamp.
contact.updated_atstring (date-time)YesISO 8601 timestamp.
keywordstring or nullYes
sourcestringYes

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

FieldTypeRequiredDescription
conversationobjectYes
conversation.idstringYesConversation id (cnv_...).
conversation.contactobjectYes
conversation.contact.idstringYesContact id (ctc_...).
conversation.contact.phonestringYesPhone number in E.164 format.
conversation.contact.first_namestring or nullYes
conversation.contact.last_namestring or nullYes
conversation.contact.companystring or nullYes
conversation.line_idstring or nullYesLine id (ln_...).
conversation.statusenumYesOne of: open, snoozed, closed.
conversation.assignee_user_idstring (uuid) or nullYes
conversation.team_idstring (uuid) or nullYes
conversation.labelsstring[]Yes
conversation.last_message_atstring (date-time) or nullYesISO 8601 timestamp.
conversation.last_messageobject or nullYes
conversation.last_message.idstringYesMessage id (msg_...).
conversation.last_message.directionenumYesOne of: outbound, inbound.
conversation.last_message.previewstringYesUp to 200 characters.
conversation.unread_countintegerYesFrom 0 to 9007199254740991.
conversation.handled_bystringYes
conversation.snoozed_untilstring (date-time) or nullYesISO 8601 timestamp.
conversation.created_atstring (date-time)YesISO 8601 timestamp.
conversation.updated_atstring (date-time)YesISO 8601 timestamp.
assignee_user_idstring (uuid) or nullYes
previous_assignee_user_idstring (uuid) or nullYes
team_idstring (uuid) or nullYes

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

FieldTypeRequiredDescription
lineobjectYes
line.idstringYesLine id (ln_...).
line.phonestringYesPhone number in E.164 format.
line.display_namestring or nullYes
line.contact_cardobjectYes
line.contact_card.namestringNoUp to 100 characters.
line.contact_card.image_urlstringNoUp to 2048 characters.
line.pool_idstring or nullYesPool id (pool_...).
line.stateenumYesOne of: provisioning, ready, warming, active, throttled, paused, quarantined, retired.
line.state_reasonstring or nullYes
line.state_changed_atstring (date-time) or nullYesISO 8601 timestamp.
line.health_scoreintegerYesFrom 0 to 100.
line.warmup_dayintegerYesFrom 0 to 9007199254740991.
line.daily_new_capintegerYesFrom 0 to 9007199254740991.
line.is_sandboxbooleanYes
line.usage_todayobjectYes
line.usage_today.daystring (date)YesUsage day in America/New_York, rolling over at 3 AM.
line.usage_today.new_contactsintegerYesFrom 0 to 9007199254740991.
line.usage_today.new_contacts_capintegerYesFrom 0 to 9007199254740991.
line.usage_today.sendsintegerYesFrom 0 to 9007199254740991.
line.usage_today.sends_capintegerYesFrom 0 to 9007199254740991.
line.created_atstring (date-time)YesISO 8601 timestamp.
fromenumYesOne of: provisioning, ready, warming, active, throttled, paused, quarantined, retired.
toenumYesOne of: provisioning, ready, warming, active, throttled, paused, quarantined, retired.
reasonstringYes

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

FieldTypeRequiredDescription
lineobjectYes
line.idstringYesLine id (ln_...).
line.phonestringYesPhone number in E.164 format.
line.display_namestring or nullYes
line.contact_cardobjectYes
line.contact_card.namestringNoUp to 100 characters.
line.contact_card.image_urlstringNoUp to 2048 characters.
line.pool_idstring or nullYesPool id (pool_...).
line.stateenumYesOne of: provisioning, ready, warming, active, throttled, paused, quarantined, retired.
line.state_reasonstring or nullYes
line.state_changed_atstring (date-time) or nullYesISO 8601 timestamp.
line.health_scoreintegerYesFrom 0 to 100.
line.warmup_dayintegerYesFrom 0 to 9007199254740991.
line.daily_new_capintegerYesFrom 0 to 9007199254740991.
line.is_sandboxbooleanYes
line.usage_todayobjectYes
line.usage_today.daystring (date)YesUsage day in America/New_York, rolling over at 3 AM.
line.usage_today.new_contactsintegerYesFrom 0 to 9007199254740991.
line.usage_today.new_contacts_capintegerYesFrom 0 to 9007199254740991.
line.usage_today.sendsintegerYesFrom 0 to 9007199254740991.
line.usage_today.sends_capintegerYesFrom 0 to 9007199254740991.
line.created_atstring (date-time)YesISO 8601 timestamp.
reasonstringYes
health_scoreintegerYesFrom 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

FieldTypeRequiredDescription
lineobjectYes
line.idstringYesLine id (ln_...).
line.phonestringYesPhone number in E.164 format.
line.display_namestring or nullYes
line.contact_cardobjectYes
line.contact_card.namestringNoUp to 100 characters.
line.contact_card.image_urlstringNoUp to 2048 characters.
line.pool_idstring or nullYesPool id (pool_...).
line.stateenumYesOne of: provisioning, ready, warming, active, throttled, paused, quarantined, retired.
line.state_reasonstring or nullYes
line.state_changed_atstring (date-time) or nullYesISO 8601 timestamp.
line.health_scoreintegerYesFrom 0 to 100.
line.warmup_dayintegerYesFrom 0 to 9007199254740991.
line.daily_new_capintegerYesFrom 0 to 9007199254740991.
line.is_sandboxbooleanYes
line.usage_todayobjectYes
line.usage_today.daystring (date)YesUsage day in America/New_York, rolling over at 3 AM.
line.usage_today.new_contactsintegerYesFrom 0 to 9007199254740991.
line.usage_today.new_contacts_capintegerYesFrom 0 to 9007199254740991.
line.usage_today.sendsintegerYesFrom 0 to 9007199254740991.
line.usage_today.sends_capintegerYesFrom 0 to 9007199254740991.
line.created_atstring (date-time)YesISO 8601 timestamp.
health_scoreintegerYesFrom 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

FieldTypeRequiredDescription
runobjectYes
run.idstring (uuid)Yes
run.workflow_idstring (uuid)Yes
run.workflow_version_idstring (uuid)Yes
run.contact_idstring or nullYes
run.statusenumYesOne of: running, waiting, completed, failed, cancelled.
run.started_atstring (date-time)YesISO 8601 timestamp.
run.finished_atstring (date-time) or nullYesISO 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

FieldTypeRequiredDescription
agent_idstring (uuid) or nullYesNull when an MCP client called handoff_to_human.
conversation_idstringYesConversation id (cnv_...).
reasonenumYesWhy, 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.
notestring or nullYesWith reason tool: the note the agent or MCP client gave. Written by a model, so treat it as untrusted text. Null otherwise.
team_idstring (uuid) or nullYes

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

FieldTypeRequiredDescription
runobjectYes
run.idstring (uuid)Yes
run.workflow_idstring (uuid)Yes
run.workflow_version_idstring (uuid)Yes
run.contact_idstring or nullYes
run.statusenumYesOne of: running, waiting, completed, failed, cancelled.
run.started_atstring (date-time)YesISO 8601 timestamp.
run.finished_atstring (date-time) or nullYesISO 8601 timestamp.
node_idstringYesThe webhook step that sent this event.
contactobject or nullYes
contact.idstringYesContact id (ctc_...).
contact.phonestringYesPhone number in E.164 format.
contact.emailstring or nullYes
contact.first_namestring or nullYes
contact.last_namestring or nullYes
contact.companystring or nullYes
contact.fieldsobjectYes
contact.labelsstring[]Yes
contact.consent_statusenumYesOne of: unknown, opted_in, opted_out.
contact.consent_sourcestring or nullYes
contact.consent_atstring (date-time) or nullYesISO 8601 timestamp.
contact.imessage_capableboolean or nullYes
contact.capability_checked_atstring (date-time) or nullYesISO 8601 timestamp.
contact.assigned_line_idstring or nullYesLine id (ln_...).
contact.owner_user_idstring (uuid) or nullYes
contact.last_inbound_atstring (date-time) or nullYesISO 8601 timestamp.
contact.last_outbound_atstring (date-time) or nullYesISO 8601 timestamp.
contact.first_reply_atstring (date-time) or nullYesISO 8601 timestamp.
contact.created_atstring (date-time)YesISO 8601 timestamp.
contact.updated_atstring (date-time)YesISO 8601 timestamp.
dataobjectYesValues 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"
}
}
}