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).
| Event | Body |
|---|---|
data_record:created | Data record |
data_record:updated | Data record |
data_record:changed | Data record |
member:updated | Member updated |
workspace:member_joined | Membership — ⚠️ not implemented |
workspace:member_left | Membership — ⚠️ not implemented |
chat:created | Chat created |
chat:summary | Chat summary |
workflow:user_message | Message |
workflow:assistant_message | Message |
workflow:execute | Workflow and task |
task:execute | Workflow and task |
task:enter | Workflow and task |
task:command | Workflow and task |
task:exit | Workflow and task |
calendar_event:created | Calendar |
calendar_event:updated | Calendar |
calendar_event:cancelled | Calendar |
calendar_event:starting | Calendar |
calendar_event:ended | Calendar |
journey:enrolled | Journey |
journey:completed | Journey |
journey_step:execute | Journey |
call:started | Call |
call:completed | Call |
call:summary | Call summary |
routine:triggered | Routine |
connector:event | Connector |
voicemail:received | Voicemail |
fax:received | Fax |
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 ofuser,system,api,webhook, orunknown.member:updatedreportsuser; data-record events currently reportunknown.fieldsUpdated— the same names, camelCased (redundant withchanged_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.