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​
| Field | Type | Description | Example |
|---|---|---|---|
id | string | Unique identifier for this event | evt_1234567890abcdef |
timestamp | string | ISO 8601 UTC timestamp of event occurrence | 2024-01-15T10:30:00Z |
event | string | Type of event that occurred | call.completed |
version | string | Webhook payload schema version | v1 |
data | object | Event-specific payload (structure varies by event type) | See event-specific sections |
Request Headers​
Webhook POST requests include these headers:
| Header | Description | Example |
|---|---|---|
Content-Type | Always JSON | application/json |
User-Agent | Audian webhook service identifier | AudianWebhook/1.0 |
X-Audian-Event | Event type (duplicate of body field) | call.completed |
X-Audian-Timestamp | Unix timestamp of delivery | 1705315800 |
X-Audian-Signature | HMAC-SHA256 signature for verification | sha256=... |
X-Audian-Delivery-ID | Unique delivery attempt identifier | delv_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:
| Reason | Code | Description |
|---|---|---|
no_answer | 408 | Destination did not answer within timeout |
declined | 603 | Call was explicitly declined/rejected |
invalid_number | 404 | Invalid or non-existent destination number |
busy | 486 | Destination line is busy |
network_error | 503 | Network connectivity issue |
internal_error | 500 | Internal 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:
- Use event
idas a deduplication key - Store processed event IDs in your database
- 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