Webhook Events Reference
Payload shape for every event the platform emits. The names and samples on this page mirror what the dispatcher actually sends — generated fromWEBHOOK_EVENT_TYPES in packages/shared/src/constants.ts
and the dispatchTenantWebhook / buildWebhookJobs call sites.
For the full catalog with one-liner descriptions, see
Webhook Events.
Event Envelope
Every webhook delivery wraps the event in a standard envelope:
When
truncated: true, data carries _truncated: true,
_original_bytes, and a small set of identifier fields (id,
message_id, conversation_id, contact_id, campaign_id,
channel) so consumers can locate the resource.
Catalog Index
Every webhook event Orbit emits — 249 in total — grouped by surface. Each row links to its long-form payload section below; combined headings (e.g. campaign create / update / delete share one section) point to the first member.Message Events
Contact Events
Campaign Events
Voice Events
Agent Events
Flow Events
Verification Events
Number / Porting Events
Conversation Events
Compliance Events
List / Segment Events
Webhook Endpoint Events
Billing Events
Commerce Events
Channel Failover Events
Push Events
Number Masking (Proxy) Events
WhatsApp Events
Supervisor Events
SIP Trunk Events
CDP Events
Other
Message Events
Every message lifecycle event (
message.sent, message.delivered,
message.failed, message.read, plus the submitted_no_receipt and
expired transitions) carries a state_class field alongside status. It
tags the transition as either "intermediate" (the status may still progress —
e.g. submitted_no_receipt, where a later genuine delivered can land) or
"terminal" (a final outcome such as delivered / failed / read), so
per-message status_callback subscribers can distinguish the two without
re-deriving the status taxonomy.Each lifecycle payload also carries is_terminal (boolean) — a convenience
discriminator that is always equivalent to state_class === "terminal". Both
derive from the same terminal-status allow-list and can never disagree, so
high-volume senders can branch on a single truthy flag (if (dlr.is_terminal))
instead of string-comparing the enum. Like state_class, is_terminal is
present only on lifecycle transitions (where status is defined).
Engagement events (email.opened / email.clicked) are not lifecycle
transitions and intentionally omit both state_class and is_terminal.message.created
Fired when an outbound message is accepted and queued for delivery.
message.sent
Fired when the message is handed off to the carrier / provider.
message.delivered
message.failed
Terminal failure reported by the carrier (or a synthesised failure
covering undelivered, rejected, expired, and submitted_no_receipt
— all of which STATUS_TO_EVENT fans out on this event; branch on the
payload’s status field to tell them apart).
error_code and error_message are optional — they appear only when the
message carries a provider-supplied failure reason. They mirror the raw code
and human-readable text the carrier / channel returned (for example a WhatsApp
Cloud API errors[0].code / .title, or an SMS / email carrier failure), and
are the same error_code / error_message values the REST message object and
SDK expose. Both are strings. A message.failed payload for a message with no
captured provider error omits both keys, so treat them as may-be-absent
rather than null. Successful transitions (message.sent,
message.delivered, message.read) clear any prior failure reason, so those
events never carry these two fields. The expired and submitted_no_receipt
transitions (also delivered via message.failed, below) follow the same rule.message.read
message.received
Inbound message from a recipient.
expired and submitted_no_receipt (delivered via message.failed)
These two terminal-non-success transitions do not dispatch dedicated
message.expired / message.submitted_no_receipt webhook events. The
dispatcher’s STATUS_TO_EVENT map
(apps/api/src/routes/webhooks/dlr-common.ts) fans both out on
message.failed, matching the per-status webhook table in
the message status lifecycle reference.
Subscribe to message.failed and branch on the payload’s status field
to distinguish them. The MESSAGE_EXPIRED / MESSAGE_SUBMITTED_NO_RECEIPT
constants in packages/shared/src/constants.ts are reserved for a future
multi-event-per-status emitter and are not emitted today.
expired — a delivery receipt arrived past the late-arrival window
(DEVOTEL_DLR_LATE_ARRIVAL_MAX_AGE_DAYS) and the message can no longer be
reconciled. state_class is terminal.
submitted_no_receipt — the provider accepted the submission (SMPP
submit_sm ACK, or a Meta DM hand-off) but no delivery receipt returned
within the grace window. state_class is intermediate — a later genuine
delivered can still land, so do not treat this as a final outcome.
message.recipient_deactivated
Fired by the nightly carrier number-deactivation sweep once per
deactivated MSISDN it flips to DNC (closing the TCPA-liability gap from
texting recycled numbers). This is not tied to a single message, so it
carries no message_id; instead it documents the deactivated number and
its provenance. carrier may be null when the feed does not report it;
source defaults to telnyx_feed.
Contact Events
contact.created
contact.updated
contact.deleted
contact.merged
contact.bulk_imported
Fired once per bulk import batch (CSV upload or POST /v1/contacts/bulk),
not once per row. The payload summarises the whole batch.
This event is emitted whenever a batch changes at least one contact — that is,
when any of
imported, updated, or failed is nonzero. Update-only batches
run in upsert mode, where every row matched an existing contact
(imported: 0, failed: 0, updated > 0), do emit this event, so you can
drive downstream sync directly from contact.bulk_imported. Only a no-op batch
where all three counts are 0 does not emit.contact.opted_in / contact.opted_out
Fired when a recipient sends a START / STOP keyword on a registered
channel.
contact.recertification_due
FCC RMD annual consent recertification window opened on a contact.
contact.phone_changed
Fired when WhatsApp (Meta) notifies Orbit that a customer moved to a
new phone number. Orbit reconciles the matching contacts onto the new
number and re-certifies their standing consent, then emits this event
with the bulk outcome. This is a different contract from
contact.recertification_due: it reports a completed re-certification
across one or more contacts, not a single contact whose annual consent
window just opened.
source_message_id is the originating WhatsApp message id and may be
null. consent_recertified_count counts the consent records Orbit
re-certified during the reconciliation and is always >= 1 for a
delivered event.
contact.segment_changed
Predictive scorer flipped a contact’s segment label (e.g.
champion → at_risk, passive → engaged). Useful for triggering
re-engagement plays from a CRM.
Campaign Events
campaign.created / campaign.updated / campaign.deleted
campaign.started
campaign.completed
Fires once when a campaign reaches a terminal state. This is the terminal event
for both success and failure paths — a campaign that never sends (its
audience couldn’t be resolved, resolved to zero contacts, or had no addressable
contacts) still emits campaign.completed, not a separate failure event. Branch
on status, not on the presence of the event.
status is one of:
completed— the campaign finished; every recipient was attempted.partially_failed— the campaign finished, but some sends failed.failed— the campaign ended without a successful send.
completion_reason, one of:
audience_resolution_failed— the audience query errored before any send.no_addressable_contacts— no contact had the address the channel needs (for example, an SMS campaign whose contacts have no phone number).statusisfailed.no_recipients— the audience resolved to zero contacts.statusiscompleted(nothing failed; there was simply nothing to send).
sent_count, delivered_count, failed_count) are present when
the campaign got far enough to attempt delivery.
Success:
campaign.paused / campaign.resumed
campaign.drip_step.completed
Single drip step finished sending to its slice.
campaign.drip.completed
Entire drip campaign finished its full schedule.
Voice Events
call.initiated
call.answered
call.completed
call.failed
call.transferred
data.transfer_type is one of blind, warm, or queue.
call.dtmf_sent
Mid-call DTMF inject through the API.
call.recording.paused / call.recording.resumed
SIPREC fork toggled mid-call.
call.secure_payment.started / call.secure_payment.completed / call.secure_payment.cancelled
Agent-assisted secure payment (masked-DTMF card capture, PCI Pal /
Sycurio-class). Emitted by VoiceService.startSecurePaymentSession /
finalizeSecurePaymentSession. While a session is started the
SIPREC recording is paused and the caller’s keypad DTMF is masked from
the agent (PCI-DSS agent descoping). The completed payload carries
only the masked summary (card_last4, card_brand) — never the
PAN.
call.secure_payment.started:
call.secure_payment.completed — masked summary only:
call.secure_payment.cancelled:
completed or cancelled) the SIPREC recording
resumes automatically — listen for call.recording.resumed.
call.recording.ready
call.transcription.ready
call.synthesis.ready (legacy inline pipeline)
call.synthesized (post-call synthesis pipeline)
Richer payload from the async synthesis scheduler. Gated on tenant
opt-in.
call.ringing
Fires when an inbound call is detected by the SIP edge but before it is answered. Useful for “live caller is dialing in” UI updates without waiting for the post-answer call.answered event.
INVITE to the SIP edge.
Retry behaviour: standard webhook retry (exponential backoff, 10 attempts over ~4.3 hours, with +0–20% jitter applied to delays — see Retry Schedule). Idempotent — call.ringing fires at most once per call. The emit is deduplicated by provider_call_sid for one hour, so a call routed across multiple ring-group legs still produces a single ringing callback.
Note (current implementation): for inbound calls, call.initiated also fires with status: "ringing", so ringing is covered even if you only listen for call.initiated. The standalone call.ringing event uses the same subscription rules as every other event — list call.ringing in your endpoint’s events array, or use "events": ["*"]. If call.ringing is missing from your delivery, confirm your endpoint subscribes to it (or *), or fall back to listening for call.initiated with status: "ringing".
Conference Events
Conference lifecycle events fire from the conference service inapps/api/src/routes/voice/. The events ride the same delivery + retry semantics as call events.
conference.created
Fires when POST /api/v1/voice/conferences succeeds.
conference.participant_joined
Fires when a new participant successfully joins (after 200 OK to the SIP INVITE for that leg).
conference.participant_left
Fires when a participant disconnects — either via DELETE API call, BYE from the carrier, or the conference moderator’s end-conference action.
hangup_reason is the free-text disconnect cause reported by the carrier/softswitch (for example Normal Call Clearing, caller hangup, or RTP timeout) — it is not a fixed enum, so match on substrings rather than exact values. sip_response_code is the numeric SIP status for the leg (e.g. 200), or null when none was received.
conference.ended
Fires when the conference’s active participant count drops to zero, or moderator hits end-conference. Always the LAST event for a conference id.
end_reason values: all_legs_terminated (the active participant count dropped to zero), manual_end (a moderator or operator ended the conference), timeout (the max-duration TTL backstop closed the room). final_status carries the conference’s terminal status (completed, failed). ended_at is the UTC timestamp the conference closed.
Recording Events
recording.started
Fires when POST /api/v1/voice/calls/{id}/recording/start (or the per-call record:true flag) starts a fresh recording.
classification tells you which surface produced the recording so you can route the event without a follow-up API call. It is one of call, conference, ai_agent, browser_softphone, or video_room. Conference recordings carry conference_id in place of call_id.
recording.completed
Alias for call.recording.ready (same payload + same recording_url). Documented separately because the call-id may not be available for conference recordings — those carry conference_id instead.
classification tells you which surface produced the recording so you can route the event without a follow-up API call. It is one of call, conference, ai_agent, browser_softphone, or video_room.
recording.failed
Fires when an upstream recording attempt fails (storage write failure, codec mismatch, AMD aborted the recording before any audio was captured).
reason is the machine-readable failure cause — read it for the reason this recording failed. failed_at is when the failure occurred (it may predate timestamp, which is when the webhook was emitted). classification tells you which surface produced the recording (call, conference, ai_agent, browser_softphone, or video_room); conference recordings carry conference_id in place of call_id.
voicemail.received
voicemail.transcript_updated
Fires when an originally-missing voicemail transcript is recovered by
the retry pipeline.
recording.retention_deleted
Fires 24h before a retention-driven GCS purge so tenants can mirror
the asset to their own archive. Payload carries a signed download URL
valid until purge_scheduled_at.
Agent Events
agent.created / agent.updated / agent.deleted / agent.deployed
agent.conversation.started
agent.conversation.ended
agent.handoff.requested
Agent escalated to a human operator.
agent.handoff_occurred
A conversation was handed to another specialist agent — either an
autonomous transfer_to_agent delegation or an operator-initiated handoff
run from an inbox macro. For macro-driven handoffs source_agent_id is
operator_macro.
agent.calendar_event.created
Fires from the agent’s create_calendar_event MCP tool.
Flow Events
flow.created / flow.updated / flow.published / flow.deleted
flow.executed
Coarse terminal signal. status is completed, failed, or
timeout.
flow.execution.started
flow.execution.completed
flow.execution.failed
Verification Events
The platform uses theverification.* prefix (not verify.*).
verification.sent
sim_swap_warning: true is
included alongside the payload above.
verification.approved
channel is
"silent".
verification.failed
verification.failed is emitted only when verification fails because the
code was entered incorrectly too many times (attempts exhausted). data
carries only the fields above — there is no reason field. Do not branch
on a reason value; treat any verification.failed event as the
attempts-exhausted case.
Pre-send security and fraud blocks are not reported through
verification.failed. They are refused before any verification row exists
(no verification_id, no OTP sent, no code checked), so reporting them as
a failed verification would deliver a phantom failure for a verification
that never started. Handle them as follows:
- Fraud blocks are delivered as their own
verification.fraud_blockedwebhook (documented below). - SIM-swap blocks and line-type blocks emit no webhook at all — they surface only as synchronous API errors. Read the error response body on the send request (see below).
verification.fraud_blocked
Emitted when the pre-send Fraud Guard refuses an OTP send (the destination
scored as fraudulent under the tenant’s fraud policy). This fires before
any verification row is created or OTP is dispatched, so the payload
deliberately has no verification_id.
403
VERIFY_FRAUD_BLOCKED API error whose details carry risk_score and
reasons.
SIM-swap and line-type blocks (no webhook)
Pre-send SIM-swap and line-type policy blocks do not dispatch any webhook. They are returned synchronously as API errors on the verification send request, so subscribers must read the error response body rather than wait for an event:- SIM-swap block —
403SIM_SWAP_DETECTED;detailscarrylast_swap_dateandblock_window_hours. - Line-type block —
422VERIFY_LINE_TYPE_BLOCKED;detailscarryline_type,channel, andpolicy_slot.
Number / Porting Events
number.purchased
reclaimed: true is included when the number was re-claimed from
the parking pool.
number.released
number.ported
porting.request.cancelled
Customer aborted an in-flight port.
Conversation Events
conversation.created
List / Segment Events
list.updated fires for both metadata edits and membership changes. Every
payload carries a change discriminator so you can tell why the list updated
without diffing state: metadata (rename / description edit), member_added
and member_removed (single membership change, each carries contact_id), or
members_added (bulk add, carries added_count). The list_id and current
name are always present. Membership events fire only when the list actually
changed — an idempotent re-add or a no-op remove emits nothing.
Webhook Endpoint Events
Billing Events
subaccount.spend_cap.threshold
Fired to the parent (reseller) org when a capped subaccount’s
month-to-date spend crosses 50%, 80%, or 100% of its monthly spend cap.
Each threshold fires once per billing period and re-arms at the start of the
next month. The 100 crossing carries enforced: true — the cap is now
stopping outbound traffic for that subaccount. Subaccounts with no cap set
are never evaluated. usage_percent is the floored month-to-date percentage
of the cap (it can exceed 100 once spend runs past the ceiling).
balance.low
balance.topped_up
credits.purchased (deprecated alias of balance.topped_up)
Same data plus a legacy credits field. Prefer balance.topped_up
for new integrations.
Channel Failover Events
channel.failover
Fired when a messaging channel’s primary provider failed and Orbit
automatically switched to a backup.
Push Events
push.delivered
Mobile / web SDK acknowledged a push was rendered on-device.
push.opened
User tapped the push and the SDK reported the open.
Number Masking (Proxy) Events
proxy.session.created
proxy.session.closed
proxy.message.forwarded
WhatsApp Events
WhatsApp events mirror Meta WABA webhook fields. Each fires after Orbit ingests the corresponding Meta callback and persists the upstream change. Payloads carry the relevantwaba_id /
phone_number_id so consumers can correlate to their own Meta tooling.
WhatsApp Calling
The lifecycle events (whatsapp.call.connected, _accepted,
_terminated) share the same payload shape; the legacy
whatsapp.call.received is also dispatched on every transition for
backward-compatible consumers.
whatsapp.call.permission_granted:
whatsapp.call.permission_revoked:
WhatsApp Templates
Sample forwhatsapp.template.approved (others share the same shape
with the relevant Meta payload echoed under details):
whatsapp.template.rejected includes details.reason with Meta’s
rejection text.
WhatsApp Account / Business / Phone
whatsapp.account.update, whatsapp.account.banned,
whatsapp.account.restricted, whatsapp.account.alert,
whatsapp.account.review_update, whatsapp.account.settings_update,
whatsapp.business.status_update, whatsapp.phone.quality_update,
whatsapp.phone.name_update, whatsapp.capability.update,
whatsapp.security.alert, whatsapp.flow.status_change,
whatsapp.quality.changed — all share the shape:
WhatsApp Data Subject Requests
whatsapp.data.delete_request follows the same shape.
WhatsApp v25 fields
whatsapp.automatic_events, whatsapp.history,
whatsapp.partner_solutions, whatsapp.payment_configuration_update,
whatsapp.smb.app_state_sync, whatsapp.smb.message_echoes,
whatsapp.tracking_events, whatsapp.user.preferences,
whatsapp.group.lifecycle_update, whatsapp.group.participants_update,
whatsapp.group.settings_update, whatsapp.group.status_update — each
carries the original Meta event payload under details:
WhatsApp Order
whatsapp.order.received — conversational-commerce order capture.
Fires once per inbound type === "order" message — i.e. when a buyer
browses the merchant’s Meta Commerce catalog inside the WhatsApp
thread and taps Send in the checkout drawer. Carries the
normalized cart so merchants can drive fulfilment / capture payment in
their own systems off a single dedicated event (previously the order
only rode the generic message.received fan-out inside message
metadata). De-duplicated against Meta webhook retries — it fires only
on the first delivery of a given wamid.
unit_price, line_total, subtotal) are minor
units of currency. When a cart mixes currencies, mixed_currency is
true and subtotal is omitted — read the per-item line_total
values instead.
Survey Events
Standalone surveys product (surveys) response webhooks. Tenant-scoped
— every emit flows through dispatchTenantWebhook, so subscribers only
see responses for their own organization.
survey.response.detractor
CXaaS closed-loop survey feedback — fires when a recipient-facing
survey response lands at detractor level (NPS ≤ 6 of 10, CSAT ≤ 6 of
10). Fires at most once per response, gated on the first recording
of a responded_at (a repeat tap of the same signed link is
idempotent and does not re-fire). Wire CRMs (Salesforce / HubSpot /
Zendesk) to this event to open a case/activity within seconds of the
detractor response.
survey_type is one of "nps", "csat", or "ces". score is the
raw response value and threshold the detractor cutoff that was
crossed; comment is null when the respondent left no free-text.
Calendly Events
Re-emitted when Calendly delivers a scheduling event to your connected Calendly integration. The fourcalendly.* types map one-to-one to
Calendly’s own event names, with the raw Calendly type repeated in
data.event_type:
All four share one payload shape:
contact_id is the Orbit contact matched or created from the invitee
email, or null when no match was made. invitee_email is null for
routing-form submissions that carry no email. scheduled_event_uri
points at the parent Calendly event; event_uri points at the specific
invitee or submission record. The delivery is idempotent on Calendly’s
event identity — a duplicate Calendly redelivery does not re-fire this
webhook.
Video Events
Real-time video room lifecycle, recording, ingress, and transcription webhooks. Tenant-scoped — every emit flows throughdispatchTenantWebhook. These mirror the Daily / Twilio Video / Agora
callbacks so a dashboard can wake on a room start, a guest join, or a
finished recording without polling the SFU.
video.room.started / video.room.ended
Fire when a video room opens and closes.
video.room.ended carries the same room_id, room_sid, and
room_name, plus a full set of end-of-call fields:
end_reason is one of failed_no_connect (room opened but no participant
ever connected), last_participant_left (emptied out normally),
operator_end (closed from the dashboard or API), timeout (hit the
configured max duration), room_finished (SFU closed the room), or
error (closed after an internal failure). It mirrors the room’s persisted
termination classification 1:1.
room_sid is omitted when the SFU never assigned one.
video.participant.joined / video.participant.left
Fire when a participant joins or leaves a room.
video.participant.left carries { room_id, identity }.
video.recording.completed / video.recording.failed
Twilio-Video-parity completion callbacks for a room recording.
recording_id is the stable id for the recording, recording_url is the
primary artifact, and recording_urls lists every artifact URL for the room
(a single-file recording collapses to one entry that matches recording_url).
recording_qc is only present once quality scoring has run.
video.recording.failed carries { room_id, recording_url: null, status, reasons }.
video.recording.degraded
Recording QC verdict — fires alongside video.recording.completed when
the artifact lands but the quality scorer flags it. Subscribe to this
when you want strict missing / degraded semantics so silent
recording loss is caught within minutes of call-end.
status is "missing" or "degraded"; recording_url is null when
no artifact was produced.
video.active_speakers.changed
Fires whenever the SFU’s active-speaker set changes. The speakers
array is pre-sorted descending by audio level, so speakers[0] is the
dominant speaker. An empty array signals silence. Not persisted per
event — aggregate downstream for talk-time rollups.
video.ingress.started / video.ingress.ended / video.ingress.failed
RTMP / WHIP / URL-pull ingress lifecycle. Subscribe to
video.ingress.started to wake live on a broadcast source connecting,
and to the ended / failed pair for terminal states.
video.ingress.ended adds status ("complete" or "error"),
error, and duration_seconds. video.ingress.failed carries a
required error string. A source that errors fires both
video.ingress.failed and video.ingress.ended with status: "error".
video.recording.transcript_ready
Async post-meeting transcription of a room recording is complete
(Deepgram). Zoom / Daily.co transcript-ready parity.
video.caption.segment
Fired per completed (final) live caption line during an active room, so a
downstream system can consume captions in real time — a live translation
relay, a compliance-capture pipeline, an accessibility feed, or live-blogging
— instead of waiting for the post-recording video.recording.transcript_ready
event. Interim/partial results are never emitted. room_id is the same room
id every other video event carries, so you can correlate segments with the
room’s lifecycle events.
Participant moderation events
Fired when a host or moderator acts on a participant, or when a participant’s in-room state changes — so you can drive a moderation UI, a compliance-logging pipeline, or webinar tooling programmatically (parity with Twilio Video / LiveKit / Agora participant-event streams). The events are:
Every payload carries
room_id (the same room id every other video event
carries, so you can correlate against video.room.started /
video.participant.joined), the server-stamped actor_user_id and
actor_role of the moderator who performed the action, and emitted_at.
Per-event fields: identity (the affected participant — absent on the
room-level video.room.spotlight_cleared), kind (audio / video, on
mute / unmute), reason (on ban), role (on role change), and hand_id
(on hand raised).
Supervisor Events
Live-call supervisor takeover (warm-handoff) lifecycle. Tenant-scoped.supervisor.takeover_requested
A supervisor initiates a takeover of a live call.
supervisor.takeover_completed
The supervisor successfully takes over and becomes the active party.
supervisor.takeover_failed
The attempt failed (e.g. a REST error during the leg-swap).
supervisor.takeover_cancelled
The takeover was cancelled (call ended before handoff completed, or the
supervisor aborted).
Voice Queue (ACD) Events
Contact-center queue-routing lifecycle (Five9 / Genesys parity). Wire these for screen-pop, after-call work, and abandonment-rescue flows without polling the queue-entries endpoint. Tenant-scoped.queue.call.queued
Fires the instant a call enters a queue.
queue.call.assigned
Fires the moment an agent is selected, before the origination call
returns — drives screen-pop.
queue.call.abandoned
Fires when the caller hangs up before assignment, or the ACD reconciler
sweeps a stuck row.
queue.alert_rule_fired
A user-configured supervisor-wallboard alert rule (action webhook)
crossed its threshold. Operators may override the emitted event slug
per rule via the rule’s delivery_config.webhook_event_type.
agent.state.changed
Contact-center agent presence transition (WFM / RTA integration
parity). Distinct from the AI-agent lifecycle events — this carries the
CCaaS from/to presence pair the supervisor wallboard sees.
wallboard.sla_warning
Fires when a queue’s predicted total wait crosses 90% of its
maxWaitSeconds SLA target. One event per breach window (30s
per-queue dedup). Wire to PagerDuty / Opsgenie / on-call Slack.
wfm.adherence_breach
Fires when an agent’s intraday adherence ratio drops below the
out-of-adherence threshold (default 85%). At most once per ~5 minutes
per agent. NICE IEX / Verint / Genesys WFM parity.
Post-Call IVR Survey Events
IVR-captured post-call CSAT / NPS responses (distinct from the standalone Surveys product above). Fires once per captured response — the de-dup path does not re-fire. Wire CRMs to auto-create activities / cases on a low CSAT or detractor NPS within seconds of the call ending. Tenant-scoped.survey.csat.response_recorded / survey.nps.response_recorded
Siblings carry the same shape — discriminate by type. CSAT uses a 1-5
scale, NPS uses 0-10.
satisfaction_bucket
(satisfied / neutral / dissatisfied); NPS responses instead carry
nps_bucket (promoter / passive / detractor). Idempotent on the
(survey_id, call_id) tuple, so a jambonz retry does not double-fire.
Conversation & Inbox Events
AI- and rule-driven conversation lifecycle for the inbox / CXaaS surface. Tenant-scoped.conversation.closed
A conversation transitioned to a closed/resolved terminal state.
conversation.disposed
The auto-disposition engine automatically closed/resolved a
conversation.
conversation.scored
A conversation received a quality-management (QM) score.
conversation.csat_detractor
A customer-facing CSAT (1-5, score under 3) or NPS (0-10, score 6 or
below) submission lands and qualifies as a detractor — a
“go-recover-this-customer” alert distinct from conversation.scored.
Gated on the first write, so a resubmission does not double-fire.
auto_reopened is true when the tenant opted into
settings.csat.auto_reopen_on_detractor and the reopen succeeded.
conversation.synthesized
A structured post-chat AI summary landed on the conversation (Intercom
Fin / Sierra parity). Mirrors the voice-surface call.synthesized
shape so a CRM/CDP can attach the summary without re-summarising.
conversation.deflected / ticket.deflected
An AI knowledge-base auto-reply resolved a conversation without a human.
Subscribe to both conversation.closed and conversation.deflected to
separate AI-resolved from human-resolved volume (AI-containment-rate /
deflection KPIs). ticket.deflected is the symmetrical alias for CRMs
that model conversations as tickets — same payload, same code path.
conversation.ai_review_needed
An AI-resolved conversation received a low CSAT — pairs every deflection
with a satisfaction check and auto-flags “AI deflected but the customer
was unhappy” for human review and retraining.
CDP Events
Customer-data-platform ingestion, identity resolution, computed-trait, and segment-membership webhooks (Segment / RudderStack / Hightouch parity). Tenant-scoped.cdp.event.ingested
Fires after a server-to-server /cdp/track or /cdp/identify payload
is verified, persisted, and identity-resolved.
cdp.identity.resolved
Fires after an identify call resolves an anonymous identity onto a
contact — for downstream CDP-to-CDP federation.
created is true when the identify call created a new contact.
cdp.tracking_plan.violation_spike
Proactive data-quality alert — fires when a tenant’s tracking-plan /
event-schema violation volume crosses the alert threshold in the
rolling window (Segment Protocols / RudderStack Data Governance parity).
contact.trait_changed
A computed-trait value transition for a single contact (Hightouch /
Segment computed-traits parity) — fires when the realtime recompute
pipeline observes a change to a contact’s trait. Critical for journey
triggers (e.g. a contact crossing ltv >= 1000).
contact.entered_segment / contact.exited_segment
Fires once per contact who crosses an operator-defined segment’s
membership boundary on its auto_refresh tick (distinct from the
predictive contact.segment_changed). Wire to Braze / Customer.io /
Iterable to mirror audience membership in real time.
contact.exited_segment carries the same shape with
direction: "exited".
Integration Health Events
Nango-managed CRM / helpdesk / automation connector health. Surfaces a previously-silent failure class so you learn the moment a tenant’s data stops flowing rather than on the next manual “Test connection”. Grouped under theintegration prefix in the subscribe UI. Tenant-scoped.
integration.auth_failed
A stored OAuth refresh token was rejected (revoked, scope changed,
account de-provisioned) — the connection silently rots while the UI
still shows “Connected”.
integration.sync_failed
A sync errored (token expired mid-sync, upstream 5xx, flow-script
failure). Fired from two paths, distinguished by reason. reason is
a stable platform-side classifier, never raw upstream provider error
text. The payload shape depends on which path fired, so branch on
reason rather than assuming a field is present.
When a specific sync run fails, the event carries the full connection
context and reason is sync_failed:
reason is
consecutive_failure_threshold:
Account & Security Events
account.fraud.alert
A customer-subscribable real-time signal that the platform’s fraud /
abuse detection acted on your own traffic — toll-fraud / IRSF
prefix block, SMS traffic-pumping, SIM-swap heuristic, voice-deepfake
high score, or an API-key geo anomaly. The customer-facing fan-out of
the same detection decision that lands in the internal fraud queue, so
you learn within minutes instead of from the next invoice. All values
are PII-scrubbed (phone PREFIX only). Tenant-scoped.
category is one of irsf_blocked, sim_swap_detected,
suspicious_campaign_send, voice_deepfake_high_score, or
api_key_geo_anomaly. severity is low / medium / high /
critical.
audit.log.created
Continuous audit-log streaming to a customer SIEM — fires once per
append to the audit log, giving SOC teams near-real-time push of every
security event to their own sink. HMAC-signed with the same envelope as
every other Orbit webhook. Tenant-scoped.
current_hash is the tamper-evident hash-chain digest of the row, so a
SIEM can later cross-check the streamed event against the audit-export
verify endpoint.
Slack Integration Events
Re-emitted from the tenant Slack webhook receiver after the inbound Slack payload is idempotently persisted, so a tenant integration can react outside Slack’s 3-second ack budget. Grouped under theslack
prefix in the subscribe UI. Tenant-scoped.
slack.slash_command
A Slack slash command (e.g. /orbit send-sms) was received.
slack.event_received
A Slack Events API callback (app_mention, message,
reaction_added, …) was received. The concrete Slack event type is
carried in data.slack_event_type rather than folded into the wire
name.
slack.interactivity
A Slack interaction (block_actions, view_submission, shortcut, …)
was received. The interaction type is carried in
data.interactivity_type.
Recording Lifecycle Events
Post-call recording QC, chaptering, and compliance-redaction webhooks (unified across SBC, AI-agent, browser-softphone, conference, and video recordings). Tenant-scoped.recording.qc_failed
A post-finalize QC failure — distinct from recording.failed, which
signals an egress abort BEFORE an artifact lands. This fires AFTER the
artifact exists but the scorer found degradation (zero-byte file,
missing path, late finalize, mime-type mismatch, sub-minimum duration).
recording.chapters.ready
The post-call auto-chaptering pipeline persisted a navigable chapter
array onto a recording.
call_id or video_room_session_id as the surface key.
recording.redaction_vault.ready
The post-call redaction-vault pipeline produced a compliance-safe
redacted copy of the call transcript. Gated on the per-tenant
settings.compliance.recording_redaction_vault.enabled opt-in. For
regulated (HIPAA / PCI-DSS / GDPR) tenants.
total_redactions is the count of regulated spans replaced across all
categories. by_category lists per-category match counts (only categories
with at least one match); type uses the redaction-engine category vocab
(credit_card, ssn, email, e164_phone, us_phone, ipv4, ipv6).
audio_redaction_segments carries the millisecond audio ranges to mute or
beep on playback — each entry is { start_ms, end_ms, category } and only
carries timings plus category, never raw transcript values. The list is
empty when word-level timings are unavailable. timestamp is the ISO-8601
time the redacted copy was produced.
Voice Analytics Events
call.hit_voicemail
An outbound call hit a voicemail / fax (answering-machine detection
result). Lets campaigns retry, log, or branch without waiting for the
full hangup event.
amd_type carries the answering-machine-detection result — one of
machine, machine_beep_detected, fax, ios_call_screening, or
ios_live_voicemail. detected_at is the ISO-8601 time detection landed.
direction is outbound or inbound, and from / to are the E.164
call legs (either may be null when the number is withheld). agent_id is
present only when the call is tied to an AI voice agent.
call.transcript_ready
Fires after post-call sentiment + keyword + topic analysis lands on the
call log — a first-class telephony CDP signal the segment-builder can
target. Fans to both webhook endpoints and CDP subscriptions.
sentiment is positive / negative / neutral / mixed;
sentiment_score ranges -1.0 to +1.0.
Verification Lifecycle Events
Per-attempt and async-fallback verification webhooks. Tenant-scoped.verification.checked
Fires on EVERY persisted check attempt — both terminal outcomes
(approved / failed) and the non-terminal pending (wrong code,
attempts remaining). Emitted before the conditional
verification.approved / verification.failed dispatch, so subscribers
see a consistent per-attempt stream. Twilio Verify
verification_check.completed parity.
outcome is approved / pending / failed.
verification.fallback_triggered
The async fallback engine successfully advanced to a new channel.
Dedup-guarded so a scheduler retry does not double-fire.
reason is timeout or delivery_failed.
verification.fallback_exhausted
The fallback engine tried every configured channel without obtaining a
verified status. The verification row is set to failed before this
fires. phone is masked (first five characters, then ****) and
exhausted_at is the ISO 8601 timestamp at which the engine gave up.
Number Lifecycle & Port-Out Events
DID inventory, compliance, and port-out lifecycle webhooks. Tenant-scoped.number.updated
Fires on every successful per-DID mutation. Suppressed for a no-op edit
(an empty PUT that changes no caller-supplied field).
number.released_compliance_timeout
A number was auto-released because its compliance deadline elapsed
without the carrier activating it. The captured monthly cost is refunded
to the tenant wallet. Distinct from the operator-initiated
number.released so you can branch on a number lost to a missed
deadline.
number.inventory.low
A low-DID-inventory alert per (country_code, area_code_prefix) bucket
whose available count fell below the threshold. Wire to a dashboard
banner or auto-reprovision via the Numbers API before run-out.
porting.request.loa_signed
A port-IN Letter of Authorization was signed in-platform — “ready to
submit to carrier”. Carries the signer details so audit dashboards
reconcile without an extra fetch.
number.port_out.requested / number.port_out.cancelled
Outbound port lifecycle — a DID Orbit hosts is being moved to (or kept
from leaving for) another carrier. Lets you reconcile downstream billing
and contact lists when a DID leaves the platform.
number.port_out.cancelled carries the same identifiers for an aborted
port-out.
SIP Trunk Events
sip_trunk.status_changed
A customer SIP trunk’s registration state transitioned between
registered and unregistered. Emitted by the register-poll
reconciler only on a genuine edge — a still-down trunk re-probed
unregistered does NOT fire, so a flapping registrar yields one event
per real transition, not one per poll tick. Tenant-scoped.
AI Agent Events
agent.response
Fires after an AI agent finalizes an LLM turn (both the synchronous
request/response API and the SSE-stream API). Tenants subscribed to
agent.response receive the same structured envelope the webhook tester
documents. Best-effort — a delivery failure never blocks the API
response. Tenant-scoped.
tokens_used is the full billed turn, not just the visible reply. It
counts the prompt context the model actually reads — your system prompt,
tool definitions, retrieved knowledge-base passages, memory, and earlier
turns in the conversation — plus the generated response. That context
dominates the total, so tokens_used is normally far larger than the
reply text suggests, and can exceed a naive character count of the
response (tokenization is model-specific; non-Latin scripts cost roughly
2–3 tokens per character). It always reconciles as
tokens_used === prompt_tokens + completion_tokens — in the example above,
1742 + 16 = 1758. Use the prompt_tokens / completion_tokens split to
attribute spend between context and generation.
agent.dnc.marked
Fires when an AI voice / text agent invokes its mark_dnc tool and the
platform persists the suppression. Wire into your CRM / consent-of-record
system.
Campaign Drip Events
campaign.drip_step.sent
Fires once per (contact, step) the moment a drip-step message is
enqueued successfully. Companion to campaign.drip_step.completed,
which fires once per step as the aggregate. Tenant-scoped.
Billing Lifecycle Events
Stripe-driven billing webhooks fanned out to tenant subscribers. The Stripe envelope is deduplicated, so subscribers receive exactly one delivery per source event. Tenant-scoped.invoice.created
A draft invoice was opened (cycle boundary, metered-usage threshold, or
manual invoice).
period_start, period_end, and due_date are Unix timestamps (or
null).
payment.failed
A PaymentIntent failed (e.g. an auto-topup card decline). Distinct from
an invoice payment failure — PI-driven failures originate from the
recharge path and carry no invoice id. Wire into dunning / customer-
service tooling.
subscription.updated
A billing-subscription state change (e.g. scheduled cancellation,
reactivation, plan change). Carries the action that drove the change.
credits.low
Deprecated alias of balance.low. Prefer balance.low for new
integrations.
WhatsApp Template & Call Lifecycle Events
whatsapp.template.quality_update
Meta reported a template quality-rating change. The Meta webhook value
fields are spread into data alongside waba_id.
whatsapp.template.category_update
Meta auto-recategorized a template between MARKETING / UTILITY /
AUTHENTICATION. Orbit also re-syncs the local templates.category so
cost tracking prices off Meta’s current category.
whatsapp.template.components_update
Meta auto-edited a template’s components for compliance (e.g. inserting
the standard opt-out hint into a MARKETING body). Orbit re-syncs the
local templates.content to Meta’s truth.
whatsapp.call.accepted / whatsapp.call.terminated
WhatsApp Business Calling lifecycle. Fan out alongside the legacy
whatsapp.call.received event. Both carry the same payload shape;
phase discriminates the lifecycle stage.
whatsapp.call.terminated carries the same shape with
phase: "terminated" and a populated duration_seconds.
Voice Eval Events
Voice-agent regression-suite lifecycle webhooks. Tenant-scoped.voice_eval.run_started / voice_eval.run_completed
Fire when an eval run begins and finishes (pass or fail).
voice_eval.run_started carries { run_id, agent_id }.
voice_eval.regression_detected
Fires when P95 latency or judge score regresses beyond the threshold vs
the baseline run.
Subscribing to Events
Register a webhook and specify which events to receive:"events": ["*"] to subscribe to all event types. Subscribing to
a name not listed on this page returns a 422.
See also
- Webhook Events Catalog — one-line description per event
- Webhook Overview — delivery, signing, and retry semantics
- Webhook Security — verifying signatures