Skip to main content

Webhook Payloads

All webhook payloads follow a consistent structure that makes them easy to parse and process in your application.

Base Payload Structure​

Every webhook POST request contains the following structure:

{
"id": "evt_1234567890abcdef",
"timestamp": "2024-01-15T10:30:00Z",
"event": "call.completed",
"version": "v1",
"data": {
// Event-specific data
}
}

Payload Fields​

FieldTypeDescriptionExample
idstringUnique identifier for this eventevt_1234567890abcdef
timestampstringISO 8601 UTC timestamp of event occurrence2024-01-15T10:30:00Z
eventstringType of event that occurredcall.completed
versionstringWebhook payload schema versionv1
dataobjectEvent-specific payload (structure varies by event type)See event-specific sections

Request Headers​

Webhook POST requests include these headers:

HeaderDescriptionExample
Content-TypeAlways JSONapplication/json
User-AgentAudian webhook service identifierAudianWebhook/1.0
X-Audian-EventEvent type (duplicate of body field)call.completed
X-Audian-TimestampUnix timestamp of delivery1705315800
X-Audian-SignatureHMAC-SHA256 signature for verificationsha256=...
X-Audian-Delivery-IDUnique delivery attempt identifierdelv_abcdef123

Call Initiated Payload​

{
"id": "evt_init_001",
"timestamp": "2024-01-15T10:30:00Z",
"event": "call.initiated",
"data": {
"call_id": "call_xyz789",
"call_uuid": "550e8400-e29b-41d4-a716-446655440000",
"call_type": "outbound",
"from": "+14155552671",
"from_name": "John Doe",
"to": "+14155552672",
"initiated_by": "api",
"initiated_at": "2024-01-15T10:30:00Z",
"tags": {
"campaign": "winter_sale",
"agent_id": "agent_123"
}
}
}

Call Answered Payload​

{
"id": "evt_answered_001",
"timestamp": "2024-01-15T10:30:05Z",
"event": "call.answered",
"data": {
"call_id": "call_xyz789",
"call_uuid": "550e8400-e29b-41d4-a716-446655440000",
"answered_by": "+14155552672",
"answered_at": "2024-01-15T10:30:05Z",
"time_to_answer": 5
}
}

Call Completed Payload​

{
"id": "evt_completed_001",
"timestamp": "2024-01-15T10:35:10Z",
"event": "call.completed",
"data": {
"call_id": "call_xyz789",
"call_uuid": "550e8400-e29b-41d4-a716-446655440000",
"from": "+14155552671",
"from_name": "John Doe",
"to": "+14155552672",
"duration": 305,
"duration_billable": 305,
"status": "completed",
"completion_reason": "normal",
"call_type": "outbound",
"initiated_at": "2024-01-15T10:30:00Z",
"answered_at": "2024-01-15T10:30:05Z",
"ended_at": "2024-01-15T10:35:10Z",
"recording_available": true,
"recording_id": "rec_xyz789",
"tags": {
"campaign": "winter_sale",
"agent_id": "agent_123"
},
"custom_fields": {
"customer_id": "cust_123",
"order_id": "ord_456"
}
}
}

Call Failed Payload​

{
"id": "evt_failed_001",
"timestamp": "2024-01-15T10:30:10Z",
"event": "call.failed",
"data": {
"call_id": "call_xyz789",
"call_uuid": "550e8400-e29b-41d4-a716-446655440000",
"from": "+14155552671",
"to": "+14155552672",
"status": "failed",
"reason": "no_answer",
"reason_code": 408,
"initiated_at": "2024-01-15T10:30:00Z",
"failed_at": "2024-01-15T10:30:10Z",
"duration": 10,
"tags": {
"campaign": "winter_sale"
}
}
}

Possible reason and reason_code pairs:

ReasonCodeDescription
no_answer408Destination did not answer within timeout
declined603Call was explicitly declined/rejected
invalid_number404Invalid or non-existent destination number
busy486Destination line is busy
network_error503Network connectivity issue
internal_error500Internal system error

Recording Completed Payload​

{
"id": "evt_rec_001",
"timestamp": "2024-01-15T10:36:00Z",
"event": "recording.completed",
"data": {
"call_id": "call_xyz789",
"call_uuid": "550e8400-e29b-41d4-a716-446655440000",
"recording_id": "rec_abc123",
"duration": 305,
"file_size": 1536000,
"file_format": "mp3",
"bitrate": 64,
"sample_rate": 16000,
"channels": 1,
"download_url": "https://api.audian.com:8443/v2/recordings/rec_abc123/download",
"processing_duration": 45,
"completed_at": "2024-01-15T10:36:00Z",
"checksum": {
"algorithm": "sha256",
"value": "abcdef0123456789"
}
}
}

IVR Input Received Payload​

{
"id": "evt_ivr_001",
"timestamp": "2024-01-15T10:31:00Z",
"event": "ivr.input_received",
"data": {
"call_id": "call_xyz789",
"call_uuid": "550e8400-e29b-41d4-a716-446655440000",
"menu_id": "menu_main",
"menu_name": "Main Menu",
"input": "2",
"input_type": "dtmf",
"digits_collected": 1,
"time_to_input": 3,
"received_at": "2024-01-15T10:31:00Z"
}
}

Call Transferred Payload​

{
"id": "evt_transfer_001",
"timestamp": "2024-01-15T10:33:00Z",
"event": "call.transferred",
"data": {
"call_id": "call_xyz789",
"call_uuid": "550e8400-e29b-41d4-a716-446655440000",
"transfer_from": "+14155552672",
"transfer_to": "+14155552673",
"transfer_type": "blind",
"transferred_at": "2024-01-15T10:33:00Z"
}
}

Parsing Payloads​

JavaScript/Node.js​

const webhook = req.body;

// Access base fields
const eventId = webhook.id;
const eventType = webhook.event;
const timestamp = new Date(webhook.timestamp);

// Access event-specific data
const callId = webhook.data.call_id;
const duration = webhook.data.duration;

// Switch on event type
switch (webhook.event) {
case 'call.completed':
console.log(`Call ${callId} completed after ${duration} seconds`);
break;
case 'recording.completed':
console.log(`Recording ready for download: ${webhook.data.download_url}`);
break;
}

Python​

import json
from datetime import datetime

webhook = request.get_json()

# Access base fields
event_id = webhook['id']
event_type = webhook['event']
timestamp = datetime.fromisoformat(webhook['timestamp'].replace('Z', '+00:00'))

# Access event-specific data
call_id = webhook['data']['call_id']
duration = webhook['data'].get('duration')

# Handle events
if webhook['event'] == 'call.completed':
print(f"Call {call_id} completed after {duration} seconds")
elif webhook['event'] == 'recording.completed':
print(f"Recording ready: {webhook['data']['download_url']}")

PHP​

$webhook = json_decode(file_get_contents('php://input'), true);

// Access base fields
$eventId = $webhook['id'];
$eventType = $webhook['event'];
$timestamp = $webhook['timestamp'];

// Access event-specific data
$callId = $webhook['data']['call_id'];
$duration = $webhook['data']['duration'] ?? null;

// Handle events
if ($webhook['event'] === 'call.completed') {
echo "Call {$callId} completed after {$duration} seconds";
}

Data Types​

Strings​

Phone numbers, IDs, and names are always strings.

{
"call_id": "call_xyz789",
"from": "+14155552671",
"from_name": "John Doe"
}

Numbers​

Durations, counts, and measurements are integers.

{
"duration": 305,
"file_size": 1536000,
"time_to_answer": 5
}

Booleans​

Feature flags are booleans.

{
"recording_available": true
}

ISO 8601 Timestamps​

All timestamps are ISO 8601 format in UTC.

{
"timestamp": "2024-01-15T10:30:00Z",
"initiated_at": "2024-01-15T10:30:00Z"
}

Objects​

Complex data is nested in objects.

{
"tags": {
"campaign": "winter_sale",
"agent_id": "agent_123"
},
"custom_fields": {
"customer_id": "cust_123"
}
}

Null Values​

Missing optional fields may be null.

{
"from_name": null,
"recording_id": null
}

Idempotence​

To safely handle retries, implement idempotent processing:

  1. Use event id as a deduplication key
  2. Store processed event IDs in your database
  3. Skip processing if you've already seen the event ID
// Example: Skip duplicate processing
const processedEvents = new Set();

async function handleWebhook(event) {
if (processedEvents.has(event.id)) {
console.log(`Event ${event.id} already processed, skipping`);
return;
}

// Process the event
await processEvent(event);

// Mark as processed
processedEvents.add(event.id);
}

Size Limits​

  • Maximum payload size: 1 MB
  • Maximum custom fields: 100 key-value pairs
  • Maximum tags: 50 key-value pairs

Next Steps​