Skip to main content

Documentation Index

Fetch the complete documentation index at: https://orbit-docs.devotel.io/llms.txt

Use this file to discover all available pages before exploring further.

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 from WEBHOOK_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:
{
  "id": "evt_abc123",
  "type": "message.delivered",
  "created_at": "2026-03-08T12:00:00Z",
  "data": { "...": "event-specific payload" }
}
FieldTypeDescription
idstringUnique event ID (idempotency key)
typestringEvent type identifier (one of the values in this reference)
created_atstringISO 8601 timestamp
dataobjectEvent-specific payload
truncatedboolean (optional)true only when the original data exceeded 256 KB and was replaced with a pointer-style envelope. Subscribers fetch the full resource via the API.
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.

Message Events

message.created

Fired when an outbound message is accepted and queued for delivery.
{
  "id": "evt_abc",
  "type": "message.created",
  "created_at": "2026-03-08T12:00:00Z",
  "data": {
    "message_id": "msg_abc123",
    "channel": "sms",
    "to": "+14155552671",
    "from": "+18005551234",
    "status": "queued",
    "created_at": "2026-03-08T12:00:00Z"
  }
}

message.sent

Fired when the message is handed off to the carrier / provider.
{
  "type": "message.sent",
  "data": {
    "message_id": "msg_abc123",
    "channel": "sms",
    "to": "+14155552671",
    "status": "sent",
    "external_id": "carrier-side-id"
  }
}

message.delivered

{
  "type": "message.delivered",
  "data": {
    "message_id": "msg_abc123",
    "status": "delivered",
    "channel": "sms",
    "timestamp": "2026-03-08T12:00:03Z"
  }
}

message.failed

Terminal failure reported by the carrier (or a synthesised failure covering undelivered / rejected).
{
  "type": "message.failed",
  "data": {
    "message_id": "msg_abc123",
    "status": "failed",
    "channel": "sms",
    "timestamp": "2026-03-08T12:00:05Z"
  }
}

message.read

{
  "type": "message.read",
  "data": {
    "message_id": "msg_abc123",
    "status": "read",
    "channel": "whatsapp",
    "timestamp": "2026-03-08T12:00:10Z"
  }
}

message.received

Inbound message from a recipient.
{
  "type": "message.received",
  "data": {
    "message_id": "msg_inb_456",
    "channel": "sms",
    "from": "+14155552671",
    "to": "+18005551234",
    "body": "What is my order status?",
    "metadata": { "raw_provider_id": "..." }
  }
}

Contact Events

contact.created

{ "type": "contact.created", "data": { "contact_id": "con_abc123" } }

contact.updated

{ "type": "contact.updated", "data": { "contact_id": "con_abc123" } }

contact.deleted

{ "type": "contact.deleted", "data": { "contact_id": "con_abc123" } }

contact.merged

{ "type": "contact.merged", "data": { "contact_id": "con_abc123" } }

contact.opted_in / contact.opted_out

Fired when a recipient sends a START / STOP keyword on a registered channel.
{
  "type": "contact.opted_out",
  "data": {
    "channel": "sms",
    "phone": "+14155552671",
    "keyword": "STOP",
    "timestamp": "2026-03-08T12:05:00Z"
  }
}

contact.recertification_due

FCC RMD annual consent recertification window opened on a contact.
{
  "type": "contact.recertification_due",
  "data": {
    "contact_id": "con_abc123",
    "consent_id": "consent_xyz",
    "channel": "sms",
    "granted_at": "2025-05-09T00:00:00Z",
    "age_days": 365,
    "phone_number": "+14155552671",
    "email": null,
    "timestamp": "2026-05-09T00:00:00Z"
  }
}

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.
{
  "type": "contact.segment_changed",
  "data": {
    "contact_id": "con_abc123",
    "previous_label": "champion",
    "new_label": "at_risk",
    "churn_risk": 0.71,
    "intent_score": 0.32,
    "propensity_score": 0.18,
    "ltv_estimate_cents": 12400,
    "signals": ["sms_open_drop", "click_drop"],
    "computed_at": "2026-03-08T12:00:00Z"
  }
}

Campaign Events

campaign.created / campaign.updated / campaign.deleted

{ "type": "campaign.created", "data": { "campaign_id": "camp_abc123" } }

campaign.started

{
  "type": "campaign.started",
  "data": { "campaign_id": "camp_abc123", "status": "running" }
}

campaign.completed

{
  "type": "campaign.completed",
  "data": { "campaign_id": "camp_abc123", "status": "completed" }
}

campaign.paused / campaign.resumed

{ "type": "campaign.paused", "data": { "campaign_id": "camp_abc123" } }

campaign.drip_step.completed

Single drip step finished sending to its slice.
{
  "type": "campaign.drip_step.completed",
  "data": {
    "campaign_id": "camp_abc123",
    "step_index": 2,
    "sent": 480,
    "failed": 12
  }
}

campaign.drip.completed

Entire drip campaign finished its full schedule.
{
  "type": "campaign.drip.completed",
  "data": {
    "campaign_id": "camp_abc123",
    "status": "completed",
    "summary": { "steps": 5, "sent": 2400, "failed": 60 }
  }
}

Voice Events

call.initiated

{ "type": "call.initiated", "data": { "call_id": "call_abc123" } }
When the call is bridged through Jambonz, the payload also carries provider and direction context:
{
  "type": "call.initiated",
  "data": {
    "call_id": "call_abc123",
    "provider": "jambonz",
    "provider_call_sid": "CA-…",
    "direction": "inbound",
    "from": "+14155552671",
    "to": "+18005551234",
    "status": "ringing",
    "timestamp": "2026-03-08T12:00:00Z"
  }
}

call.answered

{
  "type": "call.answered",
  "data": {
    "call_id": "call_abc123",
    "provider": "jambonz",
    "provider_call_sid": "CA-…",
    "direction": "inbound",
    "from": "+14155552671",
    "to": "+18005551234",
    "status": "in-progress",
    "timestamp": "2026-03-08T12:00:08Z"
  }
}

call.completed

{
  "type": "call.completed",
  "data": {
    "call_id": "call_abc123",
    "provider": "jambonz",
    "provider_call_sid": "CA-…",
    "direction": "inbound",
    "from": "+14155552671",
    "to": "+18005551234",
    "status": "completed",
    "duration_seconds": 142,
    "hangup_reason": "normal_clearing",
    "sip_response_code": 200,
    "sip_reason": "OK",
    "timestamp": "2026-03-08T12:02:22Z"
  }
}

call.failed

{
  "type": "call.failed",
  "data": {
    "call_id": "call_abc123",
    "provider": "jambonz",
    "provider_call_sid": "CA-…",
    "direction": "outbound",
    "from": "+18005551234",
    "to": "+14155559999",
    "status": "no-answer",
    "duration_seconds": 0,
    "hangup_reason": "no_user_response",
    "sip_response_code": 480,
    "sip_reason": "Temporarily Unavailable",
    "timestamp": "2026-03-08T12:00:30Z"
  }
}

call.transferred

{
  "type": "call.transferred",
  "data": {
    "call_id": "call_abc123",
    "transferred_to": "+14155558888"
  }
}

call.dtmf_sent

Mid-call DTMF inject through the API.
{
  "type": "call.dtmf_sent",
  "data": {
    "call_id": "call_abc123",
    "digits": "1234",
    "duration_ms": 100,
    "gap_ms": 80,
    "timestamp": "2026-03-08T12:01:30Z"
  }
}

call.recording.paused / call.recording.resumed

SIPREC fork toggled mid-call.
{
  "type": "call.recording.paused",
  "data": {
    "call_id": "call_abc123",
    "recording": false,
    "timestamp": "2026-03-08T12:01:00Z"
  }
}

call.recording.ready

{
  "type": "call.recording.ready",
  "data": {
    "call_id": "call_abc123",
    "recording_url": "gs://devotel-recordings/...",
    "duration_seconds": 142,
    "timestamp": "2026-03-08T12:06:00Z"
  }
}

call.transcription.ready

{
  "type": "call.transcription.ready",
  "data": {
    "call_id": "call_abc123",
    "transcript_length": 4820,
    "timestamp": "2026-03-08T12:06:30Z"
  }
}

call.synthesis.ready (legacy inline pipeline)

{
  "type": "call.synthesis.ready",
  "data": {
    "call_id": "call_abc123",
    "summary": "Customer asked about order ORD-12345…",
    "action_items": ["Send tracking link"],
    "sentiment": "neutral",
    "topics": ["order_status"],
    "timestamp": "2026-03-08T12:07:00Z"
  }
}

call.synthesized (post-call synthesis pipeline)

Richer payload from the async synthesis scheduler. Gated on tenant opt-in.
{
  "type": "call.synthesized",
  "data": {
    "call_id": "call_abc123",
    "direction": "inbound",
    "from": "+14155552671",
    "to": "+18005551234",
    "duration_seconds": 142,
    "ended_at": "2026-03-08T12:02:22Z",
    "synthesis": {
      "summary": "...",
      "sentiment": "neutral",
      "key_points": ["..."],
      "follow_up_actions": ["..."],
      "facts_learned": ["..."],
      "customer_intent": "order_status",
      "resolution": "resolved"
    },
    "synthesised_at": "2026-03-08T12:08:00Z"
  }
}

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.
{
  "type": "call.ringing",
  "data": {
    "call_id": "call_abc123",
    "provider": "jambonz",
    "provider_call_sid": "CA-…",
    "direction": "inbound",
    "from": "+14155552671",
    "to": "+18005551234",
    "status": "ringing",
    "timestamp": "2026-03-08T12:00:01Z"
  }
}
Trigger condition: carrier sends INVITE to the SIP edge. Retry behaviour: standard webhook retry (exponential 1m → 5m → 15m → 1h → 6h, 5 attempts max). Idempotent — multiple ring callbacks during a single call can fire if the call is being routed via multiple ring-group legs. Note (current implementation): ringing is currently bundled with the call.initiated event for inbound calls. The standalone call.ringing event is emitted by the underlying carrier webhook but only forwarded to tenant subscribers when the org explicitly subscribes. If call.ringing is missing from your delivery, fall back to listening for call.initiated with status: "ringing".

Conference Events

Conference lifecycle events fire from the conference service in apps/api/src/routes/voice/. The events ride the same delivery + retry semantics as call events.

conference.created

Fires when POST /v1/voice/conferences succeeds.
{
  "type": "conference.created",
  "data": {
    "conference_id": "conf_abc123",
    "name": "Q1 Planning Call",
    "max_participants": 25,
    "record": true,
    "created_by_user_id": "usr_xxx",
    "timestamp": "2026-03-08T12:00:00Z"
  }
}

conference.participant_joined

Fires when a new participant successfully joins (after 200 OK to the SIP INVITE for that leg).
{
  "type": "conference.participant_joined",
  "data": {
    "conference_id": "conf_abc123",
    "participant_id": "cpart_def456",
    "call_id": "call_xyz789",
    "phone_number": "+14155552671",
    "muted": false,
    "joined_at": "2026-03-08T12:01:14Z"
  }
}

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.
{
  "type": "conference.participant_left",
  "data": {
    "conference_id": "conf_abc123",
    "participant_id": "cpart_def456",
    "call_id": "call_xyz789",
    "duration_seconds": 312,
    "reason": "normal_disconnect",
    "timestamp": "2026-03-08T12:06:26Z"
  }
}
reason values: normal_disconnect (BYE from participant), kicked (DELETE via API), conference_ended (moderator ended), network_failure (RTP timeout).

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.
{
  "type": "conference.ended",
  "data": {
    "conference_id": "conf_abc123",
    "duration_seconds": 1840,
    "participant_count": 7,
    "ended_reason": "moderator_ended",
    "recording_url": "https://storage.devotel.io/conf/conf_abc123.mp3",
    "timestamp": "2026-03-08T12:30:40Z"
  }
}
ended_reason values: moderator_ended, last_participant_left, max_duration_reached.

Recording Events

recording.started

Fires when POST /v1/voice/calls/{id}/recording/start (or the per-call record:true flag) starts a fresh recording.
{
  "type": "recording.started",
  "data": {
    "recording_id": "rec_abc123",
    "call_id": "call_abc123",
    "dual_channel": true,
    "started_at": "2026-03-08T12:00:08Z"
  }
}

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.
{
  "type": "recording.completed",
  "data": {
    "recording_id": "rec_abc123",
    "call_id": "call_abc123",
    "duration_seconds": 142,
    "format": "mp3",
    "size_bytes": 4980000,
    "recording_url": "https://storage.devotel.io/recordings/rec_abc123.mp3",
    "url_expires_at": "2026-03-09T12:06:00Z",
    "timestamp": "2026-03-08T12:06:00Z"
  }
}

recording.failed

Fires when an upstream recording attempt fails (storage write failure, codec mismatch, AMD aborted the recording before any audio was captured).
{
  "type": "recording.failed",
  "data": {
    "recording_id": "rec_abc123",
    "call_id": "call_abc123",
    "reason": "storage_write_failed",
    "details": "GCS 503 — Service Unavailable",
    "timestamp": "2026-03-08T12:06:02Z"
  }
}

voicemail.received

{
  "type": "voicemail.received",
  "data": {
    "voicemail_id": "vm_abc123",
    "call_id": "call_abc123",
    "from": "+14155552671",
    "transcript": "Hi, please call me back at noon — I had a question about…",
    "timestamp": "2026-03-08T12:05:00Z"
  }
}

voicemail.transcript_updated

Fires when an originally-missing voicemail transcript is recovered by the retry pipeline.
{
  "type": "voicemail.transcript_updated",
  "data": {
    "voicemail_id": "vm_abc123",
    "transcript": "Recovered transcript text…"
  }
}

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.
{
  "type": "recording.retention_deleted",
  "data": {
    "source": "call",
    "source_id": "call_abc123",
    "channel": "voice",
    "created_at": "2025-05-08T00:00:00Z",
    "gcs_uri": "gs://...",
    "download_url": "https://storage.googleapis.com/...?X-Goog-Signature=...",
    "download_url_expires_at": "2026-05-10T00:00:00Z",
    "purge_scheduled_at": "2026-05-10T00:00:00Z",
    "retention_policy": {
      "voice_days": 365,
      "sms_days": 365,
      "email_days": 365,
      "whatsapp_days": 365,
      "rcs_days": 365,
      "viber_days": 365,
      "apply_hard_delete": true
    },
    "timestamp": "2026-05-09T00:00:00Z"
  }
}

Agent Events

agent.created / agent.updated / agent.deleted / agent.deployed

{
  "type": "agent.created",
  "data": {
    "agent_id": "agt_abc123",
    "name": "Support Bot",
    "type": "voice"
  }
}

agent.conversation.started

{
  "type": "agent.conversation.started",
  "data": {
    "conversation_id": "conv_001",
    "agent_id": "agt_abc123",
    "model": "gpt-4o",
    "timestamp": "2026-03-08T12:00:00Z"
  }
}

agent.conversation.ended

{
  "type": "agent.conversation.ended",
  "data": {
    "conversation_id": "conv_001",
    "agent_id": "agt_abc123",
    "reason": "Escalated to human agent",
    "end_status": "escalated",
    "timestamp": "2026-03-08T12:15:00Z"
  }
}

agent.handoff.requested

Agent escalated to a human operator.
{
  "type": "agent.handoff.requested",
  "data": {
    "conversation_id": "conv_001",
    "agent_id": "agt_abc123",
    "reason": "Customer requested human agent",
    "status": "escalated"
  }
}

agent.handoff_occurred

Agent delegated to another specialist agent through the transfer_to_agent tool.
{
  "type": "agent.handoff_occurred",
  "data": {
    "handoff_id": "ho_001",
    "source_agent_id": "agt_general",
    "target_agent_id": "agt_billing",
    "conversation_id": "conv_001",
    "reason": "Billing question",
    "summary": "User asked about invoice INV-2026-…"
  }
}

agent.calendar_event.created

Fires from the agent’s create_calendar_event MCP tool.
{
  "type": "agent.calendar_event.created",
  "data": {
    "event_id": "cal_evt_001",
    "title": "Meeting with Jane Doe",
    "description": "Discuss enterprise plan",
    "start_time": "2026-03-09T10:00:00Z",
    "end_time": "2026-03-09T10:30:00Z",
    "attendee_email": "jane@example.com",
    "conversation_id": "conv_001"
  }
}

Flow Events

flow.created / flow.updated / flow.published / flow.deleted

{ "type": "flow.created", "data": { "flow_id": "flow_abc123" } }

flow.executed

Coarse terminal signal. status is completed, failed, or timeout.
{
  "type": "flow.executed",
  "data": {
    "flow_id": "flow_abc123",
    "execution_id": "exec_001",
    "status": "completed",
    "step_count": 8
  }
}

flow.execution.started

{
  "type": "flow.execution.started",
  "data": {
    "flow_id": "flow_abc123",
    "execution_id": "exec_001",
    "node_count": 12,
    "timestamp": "2026-03-08T12:00:00Z"
  }
}

flow.execution.completed

{
  "type": "flow.execution.completed",
  "data": {
    "flow_id": "flow_abc123",
    "execution_id": "exec_001",
    "step_count": 8,
    "duration_ms": 5200,
    "timestamp": "2026-03-08T12:00:05Z"
  }
}

flow.execution.failed

{
  "type": "flow.execution.failed",
  "data": {
    "flow_id": "flow_abc123",
    "execution_id": "exec_001",
    "error": "node http_request failed: 502",
    "step_count": 3,
    "duration_ms": 1300,
    "timestamp": "2026-03-08T12:00:01Z"
  }
}

Verification Events

The platform uses the verification.* prefix (not verify.*).

verification.sent

{
  "type": "verification.sent",
  "data": {
    "verification_id": "ver_abc123",
    "channel": "sms",
    "to": "+14155552671"
  }
}
When a SIM-swap soft warning is detected, sim_swap_warning: true is included alongside the payload above.

verification.approved

{
  "type": "verification.approved",
  "data": {
    "verification_id": "ver_abc123",
    "channel": "sms",
    "to": "+14155552671"
  }
}
For Silent Verification (SIM-card-network-binding flow), channel is "silent".

verification.failed

{
  "type": "verification.failed",
  "data": {
    "verification_id": "ver_abc123",
    "channel": "sms",
    "to": "+14155552671",
    "reason": "max_attempts_exceeded"
  }
}
When the failure is a SIM-swap block, reason: "sim_swap_detected" plus last_swap_date are included.

Number / Porting Events

number.purchased

{
  "type": "number.purchased",
  "data": {
    "number_id": "num_abc123",
    "phone_number": "+14155550100"
  }
}
reclaimed: true is included when the number was re-claimed from the parking pool.

number.released

{ "type": "number.released", "data": { "number_id": "num_abc123" } }

number.ported

{
  "type": "number.ported",
  "data": {
    "porting_id": "port_001",
    "numbers": ["+14155550100", "+14155550101"],
    "carrier": "Verizon",
    "provider": "telnyx",
    "status": "submitted"
  }
}

porting.request.cancelled

Customer aborted an in-flight port.
{
  "type": "porting.request.cancelled",
  "data": {
    "porting_request_id": "port_001",
    "numbers": ["+14155550100"],
    "cancelled_from_status": "submitted"
  }
}

Conversation Events

conversation.created

{
  "type": "conversation.created",
  "data": {
    "conversation_id": "conv_001",
    "campaign_id": "camp_abc123",
    "agent_id": "agt_abc123",
    "contact_id": "con_xyz789",
    "recipient": "+14155552671",
    "source": "journey"
  }
}

List / Segment Events

{ "type": "list.created", "data": { "list_id": "list_abc" } }
{ "type": "list.deleted", "data": { "list_id": "list_abc" } }
{ "type": "segment.created", "data": { "segment_id": "seg_abc" } }
{ "type": "segment.deleted", "data": { "segment_id": "seg_abc" } }

Webhook Endpoint Events

{ "type": "webhook_endpoint.created", "data": { "endpoint_id": "wh_abc" } }
{ "type": "webhook_endpoint.updated", "data": { "endpoint_id": "wh_abc" } }
{ "type": "webhook_endpoint.deleted", "data": { "endpoint_id": "wh_abc" } }

Billing Events

balance.low

{
  "type": "balance.low",
  "data": {
    "remaining_cents": 75,
    "remaining_usd": "0.75",
    "threshold_cents": 100,
    "triggered_by": "message:msg_abc123",
    "timestamp": "2026-03-08T12:00:00Z"
  }
}

balance.topped_up

{
  "type": "balance.topped_up",
  "data": {
    "organization_id": "org_abc",
    "amount_cents": 5000,
    "currency": "usd"
  }
}

credits.purchased (deprecated alias of balance.topped_up)

Same data plus a legacy credits field. Prefer balance.topped_up for new integrations.

subscription.updated

{
  "type": "subscription.updated",
  "data": {
    "organization_id": "org_abc",
    "action": "change_plan"
  }
}
action is one of cancel, reactivate, change_plan.

Channel Failover Events

channel.failover

Fired when a messaging channel’s primary provider failed and Orbit automatically switched to a backup.
{
  "type": "channel.failover",
  "data": {
    "channel": "sms",
    "failed_provider": "telnyx",
    "active_provider": "jasmin",
    "error_message": "HTTP 502 from carrier",
    "message_id": "msg_abc123",
    "attempt_index": 1,
    "timestamp": "2026-03-08T12:00:00Z"
  }
}

Push Events

push.delivered

Mobile / web SDK acknowledged a push was rendered on-device.
{
  "type": "push.delivered",
  "data": {
    "id": "push_abc123",
    "device_token_id": "dt_abc",
    "at": "2026-03-08T12:00:01Z"
  }
}

push.opened

User tapped the push and the SDK reported the open.
{
  "type": "push.opened",
  "data": {
    "id": "push_abc123",
    "device_token_id": "dt_abc",
    "at": "2026-03-08T12:01:30Z"
  }
}

Number Masking (Proxy) Events

proxy.session.created

{
  "type": "proxy.session.created",
  "data": {
    "session_id": "psess_abc",
    "proxy_number": "+14155550100",
    "participant_a": "+14155552671",
    "participant_b": "+14155558888",
    "expires_at": "2026-03-08T13:00:00Z"
  }
}

proxy.session.closed

{
  "type": "proxy.session.closed",
  "data": {
    "session_id": "psess_abc",
    "proxy_number": "+14155550100",
    "closed_at": "2026-03-08T12:45:00Z"
  }
}

proxy.message.forwarded

{
  "type": "proxy.message.forwarded",
  "data": {
    "session_id": "psess_abc",
    "proxy_number": "+14155550100",
    "from_participant": "+14155552671",
    "to_participant": "+14155558888",
    "channel": "sms"
  }
}

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 relevant waba_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.
{
  "type": "whatsapp.call.connected",
  "data": {
    "waba_id": "1234567890",
    "phone_number_id": "0987654321",
    "meta_call_id": "wacid_…",
    "wa_call_log_id": "wacall_…",
    "direction": "inbound",
    "from": "+14155552671",
    "to": "+18005551234",
    "phase": "connect",
    "meta_event": "connect",
    "meta_status": null,
    "duration_seconds": null,
    "biz_opaque_callback_data": null
  }
}
whatsapp.call.permission_granted:
{
  "type": "whatsapp.call.permission_granted",
  "data": {
    "contact_id": "con_abc",
    "waba_id": "1234567890",
    "phone_number_id": "0987654321",
    "source": "inbound_call",
    "ttl_seconds": 86400,
    "meta_call_id": "wacid_…"
  }
}
whatsapp.call.permission_revoked:
{
  "type": "whatsapp.call.permission_revoked",
  "data": {
    "contact_id": "con_abc",
    "waba_id": "1234567890",
    "phone_number_id": "0987654321",
    "meta_call_id": "wacid_…"
  }
}

WhatsApp Templates

Sample for whatsapp.template.approved (others share the same shape with the relevant Meta payload echoed under details):
{
  "type": "whatsapp.template.approved",
  "data": {
    "waba_id": "1234567890",
    "template_id": "tpl_abc",
    "template_name": "appointment_reminder_v1",
    "language": "en_US",
    "details": { "...": "Meta event payload" }
  }
}
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:
{
  "type": "whatsapp.account.banned",
  "data": {
    "waba_id": "1234567890",
    "details": { "...": "Meta event payload" }
  }
}

WhatsApp Data Subject Requests

{
  "type": "whatsapp.data.export_request",
  "data": {
    "waba_id": "1234567890",
    "user": "+14155552671",
    "details": { "...": "Meta payload" }
  }
}
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:
{
  "type": "whatsapp.tracking_events",
  "data": {
    "waba_id": "1234567890",
    "details": { "...": "Meta event payload" }
  }
}

Subscribing to Events

Register a webhook and specify which events to receive:
curl -X POST https://orbit-api.devotel.io/api/v1/webhooks \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/orbit",
    "events": ["message.delivered", "message.failed", "call.completed"],
    "secret": "whsec_your_secret"
  }'
Use "events": ["*"] to subscribe to all event types. Subscribing to a name not listed on this page returns a 422.

See also