Skip to main content

Webhook payloads

The exact body of every webhook event. Use the table to jump to a payload; each section below shows the full JSON.

For how to subscribe, verify signatures, and handle retries, see Webhooks.

Every payload

Every body carries the same envelope — event, eventId, eventRule (id, uuid, name), and workspace (uuid, name) — and then adds the blocks below. Blocks are omitted, not nulled, when out of scope, and keys arrive alphabetically sorted (verify signatures against the raw request body). On lean shapes member.email is the account email; member:updated and data_record:* report the member-record email (normally the same).

EventBody
data_record:createdData record
data_record:updatedData record
data_record:changedData record
member:updatedMember updated
workspace:member_joinedMembership — ⚠️ not implemented
workspace:member_leftMembership — ⚠️ not implemented
chat:createdChat created
chat:summaryChat summary
workflow:user_messageMessage
workflow:assistant_messageMessage
workflow:executeWorkflow and task
task:executeWorkflow and task
task:enterWorkflow and task
task:commandWorkflow and task
task:exitWorkflow and task
calendar_event:createdCalendar
calendar_event:updatedCalendar
calendar_event:cancelledCalendar
calendar_event:startingCalendar
calendar_event:endedCalendar
journey:enrolledJourney
journey:completedJourney
journey_step:executeJourney
call:startedCall
call:completedCall
call:summaryCall summary
routine:triggeredRoutine
connector:eventConnector
voicemail:receivedVoicemail
fax:receivedFax

Data record

data_record:created, data_record:updated, data_record:changed. The changed event fires for either operation and adds a top-level operation (created or updated). dataRecord.fields mirrors the form's submitted values keyed by field slug. member is the record owner; author is who triggered the event.

{
"event": "data_record:created",
"eventId": "1c9f6a2e-7d41-4f4b-9a53-8b1c2f6d0e77",
"dataType": {
"uuid": "5f3c…",
"type": "Contact Form",
"slug": "contact_form"
},
"member": {
"id": 123,
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "+15551234567",
"externalId": "crm-9921",
"labels": ["vip", "active"],
"createdAt": "2026-01-10T09:00:00+00:00",
"updatedAt": "2026-06-16T14:30:00+00:00"
},
"dataRecord": {
"id": 4567,
"externalId": null,
"fields": {
"message": "Hello!",
"priority": "high"
},
"createdAt": "2026-06-16T14:30:00+00:00",
"updatedAt": "2026-06-16T14:30:00+00:00"
},
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "Notify CRM on new contact" },
"author": {
"id": 5,
"accountUuid": "c4d5…",
"name": "Acme Operator",
"email": "ops@acme.example"
},
"workspace": { "uuid": "9e8d…", "name": "Acme Health" }
}

For data_record:changed, the envelope begins with "operation": "updated", and an updated record also carries a change context block.

Member updated

member:updated. Fires for any profile change, including label changes — inspect context.changed_fields to see which. member.labels is the current label slug set.

{
"event": "member:updated",
"eventId": "5b7e9d13-2a86-49c0-b7f2-64a0c9e3d215",
"member": {
"id": 123,
"accountUuid": "c4d5…",
"name": "Jane Smith",
"description": null,
"phone": "+15551234567",
"email": "jane@example.com",
"externalId": "crm-9921",
"labels": ["vip", "active"],
"role": "patient",
"createdAt": "2026-01-10T09:00:00+00:00",
"updatedAt": "2026-06-16T14:30:00+00:00"
},
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "Sync label changes" },
"author": {
"id": 5,
"accountUuid": "c4d5…",
"name": "Acme Operator",
"email": "ops@acme.example"
},
"workspace": { "uuid": "9e8d…", "name": "Acme Health" },
"context": {
"changed_fields": ["phone"],
"change_source": "user",
"fieldsUpdated": ["phone"],
"oldValues": { "phone": "+15555550100" },
"newValues": { "phone": "+15555550123" },
"timestamp": "2026-06-16T14:30:00+00:00",
"old_phone": "+15555550100",
"new_phone": "+15555550123"
}
}

Membership

⚠️ Not yet implemented — these events do not fire. They can be selected and saved, but no code path emits them yet, so no webhook is ever delivered. Do not build against them; to detect new members, poll the Members API or use a data_record:* rule on your intake form.

workspace:member_joined, workspace:member_left. Envelope plus the member.

{
"event": "workspace:member_joined",
"eventId": "3a1f8c20-9d7e-4b16-9c02-5e8a1f0b7d64",
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "Sync new members to CRM" },
"workspace": { "uuid": "9e8d…", "name": "Acme Health" },
"member": {
"id": 123,
"accountUuid": "c4d5…",
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "+15551234567",
"externalId": "crm-9921"
}
}

Chat created

chat:created. Envelope plus the member and the chat identifiers.

{
"event": "chat:created",
"eventId": "0b4d6e18-3c72-4a90-8f15-9d2c7b6e0a53",
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "Log new conversations" },
"workspace": { "uuid": "9e8d…", "name": "Acme Health" },
"member": {
"id": 123,
"accountUuid": "c4d5…",
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "+15551234567",
"externalId": "crm-9921"
},
"chat": { "id": 73219, "uuid": "f7a9…" }
}

Chat summary

chat:summary. The chat-scoped summary. Fires after the conversation is summarized; carries the summary text and summaryUpdatedAt. It re-fires with a newer summaryUpdatedAt as the conversation continues — key on chat.id.

{
"event": "chat:summary",
"eventId": "e2c9a7b1-5f38-4d02-9a64-1b7c3e5d80f2",
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "Export conversation summaries" },
"workspace": { "uuid": "9e8d…", "name": "Acme Health" },
"member": {
"id": 123,
"accountUuid": "c4d5…",
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "+15551234567",
"externalId": "crm-9921"
},
"chat": { "id": 73219, "uuid": "f7a9…" },
"summary": "AI-generated summary of the conversation. Treat as PHI.",
"summaryUpdatedAt": "2026-06-16T14:35:00+00:00"
}

Message

workflow:user_message, workflow:assistant_message. Envelope plus member and chat. The message text is not included — use chat.id to fetch it over the authenticated API.

{
"event": "workflow:user_message",
"eventId": "a7f0c3d9-4e21-4b58-9c17-6d2e8b0a5f31",
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "Notify on inbound reply" },
"workspace": { "uuid": "9e8d…", "name": "Acme Health" },
"member": {
"id": 123,
"accountUuid": "c4d5…",
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "+15551234567",
"externalId": "crm-9921"
},
"chat": { "id": 73219, "uuid": "f7a9…" }
}

Workflow and task

workflow:execute, task:execute, task:enter, task:command, task:exit. Envelope plus member and chat. Same shape as Message; only event differs.

{
"event": "task:execute",
"eventId": "c8b2e5a0-1d64-4f39-9b70-3a6c9e1d7f04",
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "Fire on task step" },
"workspace": { "uuid": "9e8d…", "name": "Acme Health" },
"member": {
"id": 123,
"accountUuid": "c4d5…",
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "+15551234567",
"externalId": "crm-9921"
},
"chat": { "id": 73219, "uuid": "f7a9…" }
}

Calendar

calendar_event:created, calendar_event:updated, calendar_event:cancelled, calendar_event:starting, calendar_event:ended. Envelope plus member. The calendar event itself is used for the rule's conditions but is not included in the payload.

{
"event": "calendar_event:created",
"eventId": "9f3d1c7a-6b28-4e05-8a94-2d7b0f6c3e18",
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "Sync appointments" },
"workspace": { "uuid": "9e8d…", "name": "Acme Health" },
"member": {
"id": 123,
"accountUuid": "c4d5…",
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "+15551234567",
"externalId": "crm-9921"
}
}

Journey

journey:enrolled, journey:completed, journey_step:execute. Envelope plus member. There is no chat block — a journey has no conversation in scope.

{
"event": "journey:enrolled",
"eventId": "4d0a7e93-2c15-4b86-9f31-8b6c0d2e5a79",
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "Notify on journey enrollment" },
"workspace": { "uuid": "9e8d…", "name": "Acme Health" },
"member": {
"id": 123,
"accountUuid": "c4d5…",
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "+15551234567",
"externalId": "crm-9921"
}
}

Call

call:started, call:completed. Envelope plus member, chat, and a call block. call:completed also carries scheduledCallbacks when the AI scheduled open follow-ups. status is a Twilio-style disposition (completed, busy, no-answer, failed, canceled); answeredBy distinguishes a live answer from voicemail.

{
"event": "call:completed",
"eventId": "9d3f1c88-6b0a-45e2-9f41-c7a2b5d80e63",
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "Export call to data warehouse" },
"workspace": { "uuid": "9e8d…", "name": "Acme Health" },
"member": {
"id": 123,
"accountUuid": "c4d5…",
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "+15551234567",
"externalId": "crm-9921"
},
"chat": { "id": 73219, "uuid": "f7a9…" },
"call": {
"id": 5512,
"providerCallId": "CAe0f3…",
"status": "completed",
"direction": "outbound",
"durationSeconds": 62,
"startedAt": "2026-06-16T14:29:00+00:00",
"endedAt": "2026-06-16T14:30:02+00:00",
"answeredBy": "human"
},
"scheduledCallbacks": [
{
"uuid": "b2c3…",
"scheduledTime": "2026-06-17T17:00:00+00:00",
"targetChannel": "phone-voice",
"status": "pending",
"reason": "follow up on appointment",
"scheduledBy": "agent"
}
]
}

call fields: providerCallId is the provider's call SID (null if none was assigned); status is a Twilio-style disposition (nullable); direction is inbound/outbound; durationSeconds is connected time (0 for busy/no-answer, null while live); startedAt/endedAt are ISO-8601 (null until reached); answeredBy is the answering-machine-detection result or null. scheduledCallbacks lists still-open AI-scheduled callbacks ordered by scheduledTime; reason is the member's own words — treat it as PHI: store it encrypted at rest, keep it out of your logs, and return it only to authorized users.

The recording is never in the payload — fetch audio and transcript with chat.id over the authenticated API. call:started carries the same shape with status: "in-progress" and no scheduledCallbacks.

Call summary

call:summary. The Call shape plus the summary text and summaryUpdatedAt. This is a separate event from chat:summary — voice summaries arrive here.

{
"event": "call:summary",
"eventId": "2f7b9c04-8a13-4e6d-9b52-0c3a7e1d6f85",
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "Export call summaries" },
"workspace": { "uuid": "9e8d…", "name": "Acme Health" },
"member": {
"id": 123,
"accountUuid": "c4d5…",
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "+15551234567",
"externalId": "crm-9921"
},
"chat": { "id": 73219, "uuid": "f7a9…" },
"summary": "AI-generated summary of the call. Treat as PHI.",
"summaryUpdatedAt": "2026-06-16T14:31:00+00:00",
"call": {
"id": 5512,
"providerCallId": "CAe0f3…",
"status": "completed",
"direction": "outbound",
"durationSeconds": 62,
"startedAt": "2026-06-16T14:29:00+00:00",
"endedAt": "2026-06-16T14:30:02+00:00",
"answeredBy": "human"
}
}

Routine

routine:triggered. Fires once per target member when a routine runs. Envelope plus member. There is no chat block — a routine has no conversation in scope at trigger time. The outreach's identity comes through eventRule.name.

{
"event": "routine:triggered",
"eventId": "6c1e8a3f-0d95-4b72-9e48-7a2c5b0f9d61",
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "6-week check-in" },
"workspace": { "uuid": "9e8d…", "name": "Acme Health" },
"member": {
"id": 123,
"accountUuid": "c4d5…",
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "+15551234567",
"externalId": "crm-9921"
}
}

Connector

connector:event. Envelope plus member.

{
"event": "connector:event",
"eventId": "8e0b2d47-5c19-4a83-9f60-1d7a3e6c0b52",
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "Handle connector event" },
"workspace": { "uuid": "9e8d…", "name": "Acme Health" },
"member": {
"id": 123,
"accountUuid": "c4d5…",
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "+15551234567",
"externalId": "crm-9921"
}
}

Voicemail

voicemail:received. Workspace-global: no member and no chat block — a voicemail links to a phone number and a call, not an authenticated person. Fires once after transcription resolves.

{
"event": "voicemail:received",
"eventId": "7f2b4c19-8e3d-4a55-b1c0-2d9e6f4a8b31",
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "Route voicemails to the queue" },
"workspace": { "uuid": "9e8d…", "name": "Acme Health" },
"voicemail": {
"uuid": "3c5d…",
"callerNumber": "+15555550123",
"callerName": "Sample Caller",
"recordingDuration": 23,
"transcriptionStatus": "COMPLETED",
"transcript": "Synthetic transcript for documentation only.",
"phoneNumberId": 77,
"status": "NEW",
"createdAt": "2026-06-16T14:04:00+00:00",
"updatedAt": "2026-06-16T14:05:30+00:00"
}
}

callerNumber / callerName are spoofable caller ID — an unverified claim, not identity. transcript is null when transcriptionStatus is FAILED. The audio is never in the payload — fetch it over the authenticated voicemail API using voicemail.uuid.

Fax

fax:received. Workspace-global: no member and no chat block — a fax sender is never authenticated. Fires once after the document is durably stored.

{
"event": "fax:received",
"eventId": "5a8e1d72-4b96-4c03-9f27-e6b3a0d51c48",
"eventRule": { "id": 88, "uuid": "a1b2…", "name": "File inbound faxes" },
"workspace": { "uuid": "9e8d…", "name": "Acme Health" },
"fax": {
"uuid": "8b7a…",
"source": "documo",
"status": "RECEIVED",
"pageCount": 3,
"pdfFileId": 90211,
"tiffFileId": 90212,
"senderNumber": "+15555550188",
"senderCsid": "ACME CLINIC",
"connectionId": 14,
"phoneNumberId": null,
"failureCode": null,
"receivedAt": "2026-06-16T14:02:11+00:00",
"createdAt": "2026-06-16T14:02:14+00:00"
}
}

senderNumber / senderCsid are unauthenticated claims by the sending machine — never use them for authorization, routing to a patient record, or anything else that treats them as identity. source tells you which ingress received it: a fax-provider connection (connectionId set) or a fax-capable phone number (phoneNumberId set) — the other is null. failureCode is non-null when the fax arrived partially or failed mid-receipt; check it before treating pageCount as complete. The document is never in the payload — fetch the PDF over the authenticated Files API using pdfFileId.

The fax inbox owner (the member your workspace configured on the receiving endpoint) is deliberately not in the payload, so a recipient can't mistake them for the sender. If you need the owner, read memberId from the delivery's webhook log.


Change context

Appended to member:updated, data_record:updated, and data_record:changed (when the operation was updated). Values are the member's or record's own field values — treat as PHI.

{
"context": {
"changed_fields": ["phone"],
"change_source": "user",
"fieldsUpdated": ["phone"],
"oldValues": { "phone": "+15555550100" },
"newValues": { "phone": "+15555550123" },
"timestamp": "2026-01-15T17:05:00+00:00",
"old_phone": "+15555550100",
"new_phone": "+15555550123"
}
}
  • changed_fields — changed field names, as stored (snake_case).
  • change_source — one of user, system, api, webhook, or unknown. member:updated reports user; data-record events currently report unknown.
  • fieldsUpdated — the same names, camelCased (redundant with changed_fields; both are sent for backwards compatibility).
  • oldValues / newValues — keyed by the camelCased field name.
  • timestamp — when the payload was built, not when the change happened.
  • old_<field> / new_<field> — one pair per changed field, keyed by the as-stored (snake_case) name.

Prefer oldValues / newValues for new integrations. The flattened old_* / new_* pairs and fieldsUpdated are legacy shapes retained for existing consumers.