Skip to main content

Webhooks v2 (organization)

One endpoint, configured once for the whole organization, that receives a chosen set of event types from across the organization's workspaces. No rule to create in each workspace, and no rule to remember when a workspace is added.

This is a different system from Webhooks v1, which is per-workspace and configured as an Action. They share the signing scheme and the egress-safety rules and nothing else: v2 has its own event vocabulary, its own endpoint model, its own payload and its own delivery guarantees. Running both at once is fine and normal.

Already on v1? Webhooks v1 → v2 covers what maps across, what does not yet, and what to do about it.

Configure endpoints at Organization → Webhooks.

What you get

ScopeThe whole organization, or a named subset of its workspaces
VocabularyA closed set of activity event types — see the catalog
PayloadOne shape for every event type. Identifiers only
DeliveryA durable queue: at-least-once, retried for up to a day, held for 7 days
SigningHMAC-SHA256, same scheme as v1 — see Request signing
TestingA Send test event button on every endpoint, with an editable payload

Setting one up

  1. Go to Organization → Webhooks and create an endpoint: a name, an HTTPS URL, and the event types it subscribes to. Production rejects http:// and internal hosts — the same rule as v1.
  2. Choose which workspaces it covers. Leave the workspace list empty and it covers every workspace in the organization, including ones created later — this is the default and the common case. Link specific workspaces to narrow delivery to exactly those.
  3. Save the signing secret. It is generated for you unless you supply your own, and it is returned exactly once, in the create response. Every later read shows a masked placeholder (__SECRET_SET__); sending that placeholder back on update preserves the stored secret, and sending any other value rotates it.
  4. Press Send test event and check your handler ran. See Testing below.

Which workspaces an endpoint covers is a link list, not a mode. There is no exclude mode: "every workspace except the test ones" is an include list naming the ones you do want, and it has to be updated by hand when a workspace should join it.

Event types are drawn from the closed vocabulary below, narrowed to the types deliverable at workspace visibility — an org-only event (billing, domain verification) is rejected when you configure it, rather than accepted and then silently never sent.

Who can do what

Read endpoints, the vocabulary, the delivery logorg:webhooks — org admins hold it automatically
Create, update, delete, send a testOrg admin, with the 2FA step-up
See which org endpoints your workspace feedswebhooks:read inside that workspace

Every create, update and delete of an endpoint's configuration is written to the admin-config audit log. Status changes (pause, resume, the system's own auto-disable) live in the endpoint's status history. A test send is recorded separately again — it changes no configuration, but it is an outbound request our infrastructure made on an operator's say-so.

Payload

One shape for every event type: the raw activity event envelope, plus which endpoint delivered it. There are no per-family variants to learn.

It carries identifiers only — no member names, no message content, no DataRecord field values. Resolve what you need through the API using the ids in subject, refs and detail.

{
"id": "6c87845e-73e3-538a-9e9b-a197023cf884",
"occurred_at": "2026-01-15T14:22:00Z",
"event_type": "member.created",
"event_version": 1,
"visibility": "workspace",
"workspace_uuid": "5099b28c-b8f0-5d8f-9ea9-2d077cb89af3",
"org_uuid": "3c8c25ca-c126-52f7-8a1b-3a40ba93c609",
"account_uuid": null,
"actor": { "kind": "system", "uuid": null },
"subject": {
"kind": "member",
"uuid": "5ec1da83-d1c9-5aa7-849c-a6fcb0d18631",
"workspace_uuid": "5099b28c-b8f0-5d8f-9ea9-2d077cb89af3",
"org_uuid": null,
"account_uuid": null
},
"refs": [],
"detail": {
"schema_version": 1,
"backfill": false,
"type": "member.created",
"origin": "api",
"connection": null,
"account": null,
"capabilities": ["members:read"]
},
"dedupe_discriminator": null,
"dedupe_key": "member.created:member:5ec1da83-d1c9-5aa7-849c-a6fcb0d18631",
"endpoint": {
"uuid": "9c1f7e5a-1b2c-4d3e-8f90-a1b2c3d4e5f6",
"name": "Acme CRM sync"
}
}
FieldWhat it is
idThis occurrence's own uuid
occurred_atWhen it happened, UTC, timezone-aware
event_typeOne of the catalog values
event_versionEnvelope version. Bumped only if the envelope's own shape changes
visibilityAlways workspace on a delivered event
workspace_uuidThe workspace the event rolled up under
org_uuidAlways the delivering endpoint's organization, whatever the underlying event recorded
account_uuidThe account, when the occurrence names one
actorWho caused it: {kind, uuid}, uuid null exactly when kind is system
subjectThe entity the event is about, as a typed reference
refsOther entities the occurrence names, same shape as subject
detailThe typed payload — varies by event type, see the catalog
dedupe_keyThe idempotency key. Same value on every retry. Dedupe on it
endpointWhich of your endpoints delivered this

Every reference — subject, each of refs, and the ref-shaped fields inside detail — is {kind, uuid, workspace_uuid, org_uuid, account_uuid}. Never an inline name, never a value.

detail always carries type (equal to the envelope's event_type), schema_version, and backfill (true when the row was synthesized from canonical data by a backfill job rather than observed live). The catalog below lists what each type adds.

event_type, visibility, subject.kind and every enum inside detail come from closed sets. Treat an unrecognised value as "ignore, don't error" — these vocabularies grow.

What never arrives

  • Account-private occurrences. An event reaches an endpoint only by the workspace it rolls up under. Chat events are workspace occurrences, direct messages included — a DM is your team talking to a Member. The exception is a Member's own tool sessions (a manager chat, an agent job chat), which roll up under that account rather than a workspace, so they are never delivered here even when the endpoint's workspaces would otherwise include them.
  • Org-only events. Billing and domain-verification types exist in the vocabulary but are not deliverable at workspace visibility, so they cannot be subscribed to.
  • Delivery outcomes. A failed delivery is not itself an event. There is no webhook.failed in the vocabulary and there never will be; a delivery outcome cannot be subscribed to.
  • Field-level detail. Raising deliveries to the field-level detail v1 payloads carry (include_payload) is not available yet.

Delivery

MethodPOST
Content-Typeapplication/json
BodyJSON with alphabetically sorted keys, UTF-8
Signature headerX-Webhook-Signature — HMAC-SHA256, see Request signing
Timeout30 seconds total, connection included
RedirectsNot followed
SuccessAny 2xx
RetriesUp to 12 attempts, exponential backoff from 60 seconds, doubling to a 1-hour cap

Deliveries run on a fixed once-a-minute schedule rather than firing the instant the event happens. This is a durable queue, not a live push: a burst of activity is spread across ticks rather than delivered all at once.

4xx is retried here. Unlike v1, no HTTP status is by itself a permanent rejection — timeouts, connection errors, 3xx, 4xx and 5xx are all retried on the same schedule. Return 2xx as soon as you have durably accepted the payload, and do your processing asynchronously.

At-least-once, not exactly-once. dedupe_key is the same value on every retry and on any replay. Dedupe your handler on it.

Two failures skip the retry schedule and dead-letter immediately, because retrying cannot fix either: a destination that fails the egress-safety check (the hostname is resolved and the connection pinned to the address actually vetted), and a missing or corrupted signing secret.

When your receiver stays broken

A delivery dead-letters after 12 attempts. After 5 dead-lettered deliveries in 24 hours, the endpoint is automatically disabled and no new sends start until an operator resumes it; the org UI explains why from the endpoint's status history.

Nothing is lost while paused or disabled. New events keep queueing and the backlog delivers once the endpoint is active again — for up to 7 days. A queued delivery older than that expires, shows in the delivery log as expired, and never counts toward the auto-disable threshold.

An endpoint's status is active, paused (an operator's choice) or disabled (the circuit breaker). Every change, including the initial active, records who made it (null when the system did), why, and when.

Deleting an endpoint stops delivery immediately, but its already-queued deliveries are not deleted with it — they simply age out under the same 7-day retention.

Delivery and attempt rows are an operational troubleshooting log kept for 30 days, not the compliance record. Configuration changes stay in the admin-config audit log and status transitions stay in the status history after delivery details age out.

Testing

Every endpoint has a Send test event action (⋯ menu on the endpoint card). Pick an event type, and you get the real payload for it, filled with example identifiers. Edit anything you like — put in an id your handler will actually resolve, drop a field to see what your parser does without it — and send. You get back the HTTP status, how long it took, and your own server's reply body.

The test goes through exactly the same code a real delivery does: the same egress vetting, the same address pinning, the same canonicalization, the same HMAC, the same outcome classification. If your receiver accepts the test, it accepts the real thing.

What it deliberately does not do:

  • It queues nothing. No delivery row, no attempt, no retry.
  • It cannot disable your endpoint. Test against a receiver that 500s twenty times and the endpoint's failure budget is untouched.
  • It does not fill your delivery log, so the log stays useful for diagnosing real traffic.

It works on a paused or disabled endpoint too — those states hold the queue back, and "is it fixed yet?" is exactly the question you have while an endpoint is paused.

The delivered body carries "test": true at the top level. It is in the body rather than a header on purpose: the signature covers the body, so a receiver that wants to discard test traffic can key on a value it has verified.

The event-type picker lists the types this endpoint is subscribed to, with find-as-you-type narrowing. To try a handler against a type before you commit to the subscription, subscribe to it, test, then unsubscribe — the test send itself never changes the subscription.

The direct test API is wider than the UI picker: POST .../test still accepts any event type in the closed subscribable vocabulary, subscribed or not. The picker narrows the offer; the API keeps the contract.

API

RouteWhat it does
GET/POST /api/v2/o/{org_uuid}/webhooksList and create endpoints
GET/PATCH/DELETE /api/v2/o/{org_uuid}/webhooks/{endpoint_uuid}Read, update, delete one
GET /api/v2/o/{org_uuid}/webhooks/event-typesThe closed subscribable vocabulary
GET /api/v2/o/{org_uuid}/webhooks/event-types/{event_type}/sampleA representative payload for one type
POST /api/v2/o/{org_uuid}/webhooks/{endpoint_uuid}/testSend one payload now; returns the outcome
GET /api/v2/o/{org_uuid}/webhooks/{endpoint_uuid}/deliveries30-day delivery log for one endpoint
GET /api/v2/w/{workspace_uuid}/webhook-logs/org-endpointsFrom inside a workspace: which org endpoints it feeds

Notes on the shapes:

  • workspaceUuids is the endpoint's current sorted link list. limitedToWorkspaces distinguishes a limited endpoint whose last workspace was deleted from whole-org coverage. On update, omitting the field leaves the links unchanged; sending [] switches the endpoint to every workspace.
  • Each endpoint carries statusHistory, most recent first, up to 20 entries.
  • Endpoint reads return a destination fingerprint (scheme://host plus a short digest of the full URL) rather than the URL — webhook paths and queries routinely carry per-tenant tokens. The full URL is returned only in the create response, like the one-time secret.
  • The delivery log pairs each delivery (event type, when it occurred, its dedupe key, its workspace, queue status, when it next retries) with every attempt made at it, oldest first (attempt number, start and finish, outcome, failure class and error type, HTTP status).
  • The workspace-side read shows name, destination fingerprint, status, event types and whether the endpoint is limited — never the signing secret, never delivery contents. It is available to any member holding webhooks:read, not only admins.

Event catalog

data_record.created and data_record.updated are emitted (GRA-8876), but only for a Form that has opted in. The opt-in is the per-DataType webhook_events_enabled setting, off by default: a record write in a Form that has not opted in produces no event at all — not a row that is later filtered — so an endpoint subscribed to these types receives nothing from that Form. Turn it on per Form before relying on record events.

Which surfaces emit, today. Records written through the REST API, the AI data tools, CSV/import, signup, and the enrollment /submit funnel all reach one shared post-commit emitter, so an opted-in Form's writes from those surfaces publish. Connector-synced records do not emit yet: the eCW/Elation sync bridge and Monday record-target inbound write records without reaching that post-commit step, and the enrollment /track path and the capture-only inquiry path write funnel telemetry that deliberately dispatches nothing. Those gaps are follow-up work — do not rely on data_record.* for a Form whose rows a connector writes until they land.

The payload is IDs and field slugs, never field values: subject names the data_record, detail.data_type names the Form, and detail.field_slugs lists the slugs that were written or changed. A record's values are where its PHI lives, so a consumer that needs contents reads the record back through the API under its own authorization. If you still need the v1 per-workspace record rules for payload contents, see v1's data-record events.

chat.summary and call.summary are emitted, and they carry no content. They signal that a summary was persisted: chat.summary for a Chat on any channel, call.summary for a phone-channel Chat only — the historical v1 call:summary contract. The envelope names the Chat (chat.summary) or the call (the Chat's most recent PhoneCall, for call.summary) as subject, and the detail carries the channel plus, for the call type, the Chat reference when its identifier is a UUID. The summary text is PHI and is deliberately never in the payload; a receiver that needs it reads the Chat back through the API under its own authorization. Both types are unconditional — there is no per-Chat opt-in.

There are 42 subscribable event types, in six families. Every one delivers the envelope described above; what changes between them is subject and detail.

People

Who is in a workspace and what is recorded about them — the member lifecycle, role and label changes, attribute edits, and the imports that do all of those in bulk.

member.created

subject is a member reference.

detail fieldTypeAlways present
originMemberOriginno
connectionentity refno
accountentity refno
capabilitieslist of ActivityCapabilityno

member.removed

subject is a member reference.

detail fieldTypeAlways present
reasonMemberRemovalReasonyes
accountentity refno

member.role_changed

subject is a member reference.

detail fieldTypeAlways present
accountentity refno
capabilitieslist of ActivityCapabilityno

member.labels_changed

subject is a member reference.

detail fieldTypeAlways present
addedlist of entity refno
removedlist of entity refno

member.attributes_changed

subject is a member reference.

detail fieldTypeAlways present
field_slugslist of stringno
data_typeentity refno

import.started

subject is a import reference.

detail fieldTypeAlways present
sourceImportSourceyes
connectionentity refno
expected_rowsintegerno

import.completed

subject is a import reference.

detail fieldTypeAlways present
sourceImportSourceyes
connectionentity refno
created_countintegerno
updated_countintegerno
skipped_countintegerno
failed_countintegerno
duration_msintegerno

data_record.created

subject is a data_record reference.

detail fieldTypeAlways present
field_slugslist of stringno
data_typeentity refno

data_record.updated

subject is a data_record reference.

detail fieldTypeAlways present
field_slugslist of stringno
data_typeentity refno

Conversations

Every exchange with a person, in any channel: chats, escalations, calls, voicemail, and a Member's run through a Workflow (an Assignment).

assignment.started

subject is a assignment reference.

detail fieldTypeAlways present
workflowentity refno
revision_numberintegerno
triggerAssignmentTriggerno

assignment.concluded

subject is a assignment reference.

detail fieldTypeAlways present
workflowentity refno
outcomeAssignmentConclusionyes
task_countintegerno
completed_task_countintegerno
duration_msintegerno

chat.started

subject is a chat reference.

detail fieldTypeAlways present
laneChatLaneyes
channelChatChannelyes
initiated_byActorKindyes

chat.awaiting_human

subject is a chat reference.

detail fieldTypeAlways present
reasonslist of ChatAttentionReasonyes
cleared_reasonslist of ChatAttentionReasonno

chat.human_replied

subject is a chat reference.

detail fieldTypeAlways present
cleared_reasonslist of ChatAttentionReasonno
waited_msintegerno
reply_message_uuiduuidno
reply_memberentity refno

chat.call_ended

subject is a chat reference.

detail fieldTypeAlways present
reasonslist of ChatAttentionReasonno
cleared_reasonslist of ChatAttentionReasonno

chat.summary

subject is a chat reference.

detail fieldTypeAlways present
channelChatChannelyes

escalation.opened

subject is a escalation reference.

detail fieldTypeAlways present
reasonEscalationReasonyes
chatentity refno

escalation.resolved

subject is a escalation reference.

detail fieldTypeAlways present
resolutionEscalationResolutionyes
open_msintegerno
chatentity refno

call.started

subject is a call reference.

detail fieldTypeAlways present
directionCallDirectionyes
phone_numberentity refno

call.completed

subject is a call reference.

detail fieldTypeAlways present
directionCallDirectionyes
outcomeCallCompletionDispositionyes
duration_msintegerno
recordedbooleanno

call.missed

subject is a call reference.

detail fieldTypeAlways present
directionCallDirectionyes
reasonMissedCallReasonyes

call.summary

subject is a call reference.

detail fieldTypeAlways present
channelChatChannelyes
chatentity refno

voicemail.received

subject is a voicemail reference.

detail fieldTypeAlways present
duration_msintegerno
phone_numberentity refno
transcribedbooleanno

voicemail.listened

subject is a voicemail reference.

detail fieldTypeAlways present
waited_msintegerno

Journeys

A Member's progress through a Journey. Four of the five name the member as their subject; only journey.activated names the Journey.

journey.enrolled

subject is a member reference.

detail fieldTypeAlways present
journeyentity refyes
triggerJourneyEnrollmentTriggerno

journey.unenrolled

subject is a member reference.

detail fieldTypeAlways present
journeyentity refyes
reasonJourneyExitReasonno

journey.completed

subject is a member reference.

detail fieldTypeAlways present
journeyentity refyes
step_countintegerno
duration_msintegerno

journey.step_transitioned

subject is a member reference.

detail fieldTypeAlways present
journeyentity refyes
stepentity refyes
transitionJourneyTransitionyes
from_statusJourneyStepStatusno
to_statusJourneyStepStatusyes

journey.activated

subject is a journey reference.

detail fieldTypeAlways present
step_countintegerno

Automation

The machine working on its own, with nobody on the other end: Routine runs and a Workflow being published.

routine.started

subject is a routine reference.

detail fieldTypeAlways present
routineentity refyes
triggerRoutineTriggeryes

routine.completed

subject is a routine reference.

detail fieldTypeAlways present
routineentity refyes
outcomeRoutineOutcomeyes
duration_msintegerno

routine.failed

subject is a routine reference.

detail fieldTypeAlways present
routineentity refyes
failureRoutineFailureyes
duration_msintegerno

workflow.published

subject is a workflow reference.

detail fieldTypeAlways present
revisionentity refno
revision_numberintegeryes
task_countintegerno

Operators

Who is available and who is in which operator group.

presence.went_online

subject is a member reference.

detail fieldTypeAlways present
devicePresenceDeviceno
operator_groupslist of entity refno
live_workspaceslist of entity refno

presence.went_offline

subject is a member reference.

detail fieldTypeAlways present
devicePresenceDeviceno
online_duration_msintegeryes
reasonPresenceOfflineReasonyes

operator_group.member_added

subject is a operator_group reference.

detail fieldTypeAlways present
groupentity refyes
memberentity refyes

operator_group.member_removed

subject is a operator_group reference.

detail fieldTypeAlways present
groupentity refyes
memberentity refyes

Administration

The account around the work: phone numbers, domains, settings, subscriptions and purchases.

member_subscription.started

subject is a member_subscription reference.

detail fieldTypeAlways present
plan_slugstringyes
intervalBillingIntervalyes

member_subscription.canceled

subject is a member_subscription reference.

detail fieldTypeAlways present
plan_slugstringno
reasonSubscriptionCancelReasonyes

phone_number.provisioned

subject is a phone_number reference.

detail fieldTypeAlways present
number_kindPhoneNumberKindno
countrystringno
capabilitieslist of PhoneCapabilityno

phone_number.archived

subject is a phone_number reference.

detail fieldTypeAlways present
reasonPhoneNumberArchiveReasonyes

config.updated

subject is a workspace_config reference.

detail fieldTypeAlways present
areaConfigAreayes
actionConfigActionyes
field_slugslist of stringno
timezone_beforestringno
timezone_afterstringno

Closed vocabularies

Every value below comes from a closed set that grows by review. Treat a value you do not recognise as "ignore, do not error" — a new member is an additive change and will not be announced as a breaking one.

ActivityCapability

assignments:read, chats:read, journeys:read, members:read, operator:read, org:read, phones:read, records:read, workflows:read, workspace:read

ActorKind

account, agent, connection, member, org_account, system

AssignmentConclusion

success, failed, referred, caller_hangup, member_unsubscribe, admin_canceled, expired, system_error, busy, no_answer

AssignmentTrigger

api, journey, manual, rule, schedule

BillingInterval

month, quarter, year, once

CallCompletionDisposition

completed, busy, no-answer, failed, canceled

CallDirection

inbound, outbound

ChatAttentionReason

agent_handed_off, escalation_open, legacy_unattributed, posture_awaits_human, unanswered_inbound

ChatChannel

email, internal, sms, voice, web, whatsapp

ChatLane

agent_job, dm, manager, workspace

ConfigAction

created, deleted, updated

ConfigArea

business_hours, escalation_settings, inbox, phone_number, workspace_settings

EscalationReason

agent_requested, member_requested, policy, tool_approval

EscalationResolution

answered, approved, declined, expired

ImportSource

api, connector_sync, csv

JourneyEnrollmentTrigger

api, import, manual, rule

JourneyExitReason

completed, manual, member_removed, rule

JourneyStepStatus

pending, eligible, in_progress, completed, skipped, failed, timed_out

JourneyTransition

activated, advanced, failed, skipped

MemberOrigin

api, connector, import, manual, signup, system, org_api_key

MemberRemovalReason

admin, deleted, merged, retention

MissedCallReason

after_hours, no_answer, no_operator_online, rejected, timeout

PhoneCapability

fax, mms, sms, voice

PhoneNumberArchiveReason

admin, ported_out, released

PhoneNumberKind

local, mobile, short_code, toll-free

PresenceDevice

browser, phone, sip, unknown

PresenceOfflineReason

disconnect, manual, timeout

RoutineFailure

canceled, error, timeout

RoutineOutcome

no_action, partial, succeeded

RoutineTrigger

api, manual, rule, schedule

SubscriptionCancelReason

admin, migration, payment_failure, replaced, requested