English
English
Appearance
English
English
Appearance
When a call occurs, Zorio POSTs an event to the URL you registered (HMAC-signed).
| Event | When it fires |
|---|---|
autocall.lead.added | A lead was successfully pushed into a campaign |
autocall.lead.audio_ready | TTS audio rendering finished, the lead is ready to dial |
autocall.call.initiated | The engine started originate |
autocall.call.answered | B-leg answered |
autocall.dtmf.pressed | The called party pressed a key (1 event per keypress) |
autocall.call.transferred | DTMF action = queue → transferred to the queue successfully |
autocall.call.completed | The call ended (any outcome) |
autocall.lead.completed | The lead has finished every retry (final status) |
autocall.campaign.completed | The campaign has no pending leads left |
autocall.tts.render_failed | The TTS provider errored out and the lead was skipped |
Every event uses the same envelope structure:
| Field | Type | Description | Valid values |
|---|---|---|---|
event | string | Full event name | autocall.<entity>.<action> |
timestamp | datetime | When Zorio published the event (ISO 8601 UTC) | |
tenant_id | integer | Account ID receiving the webhook | |
data | object | Event-specific payload (see each event below) |
data fields per event autocall.lead.added / autocall.lead.audio_ready / autocall.lead.completed | Field | Type | Description | Valid values |
|---|---|---|---|
lead_id | integer | Lead ID in AutoCall | |
campaign_id | integer | ID of the campaign containing the lead | |
phone | string | Lead phone number | E.164 or Vietnamese domestic |
external_ref | string|null | External CRM reference (if you pushed one) | |
audio_url | string|null | TTS audio URL (only present on audio_ready) | |
final_status | string | Final state (only present on lead.completed) | completed / failed / dnc / max_attempts_reached |
total_attempts | integer | Total dial attempts (only on lead.completed) | >= 1 |
autocall.call.initiated | Field | Type | Description | Valid values |
|---|---|---|---|
uuid | string | Call UUID | |
campaign_id | integer | Campaign ID | |
lead_id | integer | Lead ID | |
phone | string | Number being dialed | |
attempt_number | integer | Which attempt this is | >= 1 |
caller_id | string | Caller-ID used for the call | |
started_at | datetime | Originate time (ISO 8601 UTC) |
autocall.call.answered Includes all fields of call.initiated plus:
| Field | Type | Description | Valid values |
|---|---|---|---|
answered_at | datetime | Time the B-leg answered (ISO 8601 UTC) |
autocall.dtmf.pressed | Field | Type | Description | Valid values |
|---|---|---|---|
uuid | string | Call UUID | |
campaign_id | integer | Campaign ID | |
lead_id | integer | Lead ID | |
phone | string | Number being called | |
digit | string | Key just pressed | 0-9, *, # |
pressed_at | datetime | DTMF receive time (ISO 8601 UTC) |
autocall.call.transferred | Field | Type | Description | Valid values |
|---|---|---|---|
uuid | string | Call UUID | |
campaign_id | integer | Campaign ID | |
lead_id | integer | Lead ID | |
transfer_target | string | Transfer destination | queue:<queue_name> / extension:<ext> |
transferred_at | datetime | Time the transfer succeeded (ISO 8601 UTC) |
autocall.call.completed | Field | Type | Description | Valid values |
|---|---|---|---|
uuid | string | Call UUID | |
campaign_id | integer | Campaign ID | |
lead_id | integer | Lead ID | |
phone | string | Number that was called | |
attempt_number | integer | Which attempt this is | >= 1 |
started_at | datetime | Originate time (ISO 8601 UTC) | |
answered_at | datetime|null | Answer time (null if not answered) | |
ended_at | datetime | Hangup time (ISO 8601 UTC) | |
duration_sec | integer | Total call duration (seconds) | >= 0 |
result | string | Outcome classification | connected / no_answer / busy / failed / voicemail / dnc_blocked |
hangup_cause | string | Standard Q.850 hangup code from Zorio PBX | NORMAL_CLEARING / NO_ANSWER / USER_BUSY / CALL_REJECTED / ... |
dtmf_pressed | string|null | The last key the user pressed | |
final_action | string|null | Final action executed (per the DTMF script) | queue:<name> / hangup / dnc_added / null |
autocall.campaign.completed | Field | Type | Description | Valid values |
|---|---|---|---|
campaign_id | integer | ID of the campaign that just finished | |
campaign_name | string | Campaign name | |
total_leads | integer | Total leads in the campaign | >= 0 |
total_connected | integer | Leads that answered successfully | >= 0 |
completed_at | datetime | Campaign completion time (ISO 8601 UTC) |
autocall.tts.render_failed | Field | Type | Description | Valid values |
|---|---|---|---|
lead_id | integer | ID of the skipped lead | |
campaign_id | integer | Campaign ID | |
provider | string | TTS provider that errored | tts_provider_01 / tts_provider_02 / tts_provider_03 / local |
error_code | string | Error code | RATE_LIMIT / INVALID_VOICE / QUOTA_EXCEEDED / UNKNOWN |
error_message | string | Error description | |
failed_at | datetime | TTS failure time (ISO 8601 UTC) |
Webhooks are NOT registered via API
Per Zorio's security policy, webhook configuration is done manually through the Admin Console. The Public API only serves AutoCall business operations (campaign / lead / call lifecycle / reports).
Why:
Onboarding workflow:
Changing the configuration (changing URL, adding events, rotating the secret) is also done through the Admin Console, not via the API.
autocall.call.completed) {
"event": "autocall.call.completed",
"timestamp": "2026-06-24T08:32:47Z",
"tenant_id": 1,
"data": {
"uuid": "093e1024-...",
"campaign_id": 36,
"lead_id": 78912,
"phone": "84912345678",
"attempt_number": 1,
"started_at": "2026-06-24T08:32:00Z",
"answered_at": "2026-06-24T08:32:05Z",
"ended_at": "2026-06-24T08:32:47Z",
"duration_sec": 42,
"result": "connected",
"hangup_cause": "NORMAL_CLEARING",
"dtmf_pressed": "2",
"final_action": "queue:support_queue"
}
}| Header | Type | Description | Value |
|---|---|---|---|
X-Zorio-Signature | string | HMAC-SHA256 of the raw body using the webhook secret | sha256=<hex> |
X-Zorio-Timestamp | integer | Epoch seconds (UTC) — clients should reject if skew > 300s | |
X-Zorio-Event | string | Convenience copy of the event field | autocall.<entity>.<action> |
X-Zorio-Delivery | string | Delivery UUID — used for dedup on retry | UUID v4 |
Content-Type | string | Content type | application/json; charset=utf-8 |
Verify HMAC
Pseudo-code:
expected = "sha256=" + hex(hmac_sha256(secret, raw_request_body))
if expected != header["X-Zorio-Signature"]: rejectDo NOT verify against the parsed JSON — you must use the raw body bytes.
Client-side idempotency
The client MUST ensure the endpoint is idempotent (check the X-Zorio-Delivery UUID) because the webhook may be resent if HTTP timed out on Zorio's side after the client already executed it.