Webhook API Reference
Technical reference for all webhook events sent by the voice agent platform.
Webhook Delivery
Request Format
POST {your_webhook_url}
Content-Type: application/json
X-Webhook-Signature: sha256=abc123...
X-Webhook-Event: call.ended
X-Webhook-Timestamp: 1704067200
Signature Verification
func verifyWebhook(payload []byte, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}
import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Event Types
call.started
Triggered when a call begins.
{
"event": "call.started",
"timestamp": "2024-01-01T12:00:00Z",
"data": {
"call_id": "call_abc123",
"agent_id": "agent_xyz",
"direction": "inbound",
"from": "+1234567890",
"to": "+0987654321",
"provider": "twilio",
"stream_sid": "MZxxx",
"call_sid": "CAxxx",
"workspace_id": "ws_123"
}
}
call.ended
Triggered when a call completes.
{
"event": "call.ended",
"timestamp": "2024-01-01T12:05:00Z",
"data": {
"call_id": "call_abc123",
"agent_id": "agent_xyz",
"duration_seconds": 300,
"end_reason": "user_hangup",
"disposition": "success",
"transcript": {
"summary": "Customer inquired about order status...",
"turns": [
{
"role": "assistant",
"content": "Hello! How can I help you today?",
"timestamp": "2024-01-01T12:00:01Z"
},
{
"role": "user",
"content": "I want to check my order status",
"timestamp": "2024-01-01T12:00:05Z"
}
]
},
"metadata": {
"functions_called": ["get_order_status"],
"transfer_occurred": false,
"recording_url": "https://..."
}
}
}
End Reasons:
| Reason | Description |
|---|---|
user_hangup |
User ended the call |
agent_hangup |
Agent completed and ended |
transfer |
Call was transferred |
error |
Call ended due to error |
timeout |
No activity timeout |
max_duration |
Maximum call duration reached |
call.transferred
Triggered when a call is transferred.
{
"event": "call.transferred",
"timestamp": "2024-01-01T12:03:00Z",
"data": {
"call_id": "call_abc123",
"transfer_to": "+1555123456",
"transfer_type": "blind",
"reason": "customer_request",
"context": "Customer requested to speak with a manager"
}
}
transcript.updated
Real-time transcript updates during the call.
{
"event": "transcript.updated",
"timestamp": "2024-01-01T12:01:30Z",
"data": {
"call_id": "call_abc123",
"turn": {
"role": "user",
"content": "I want to check my order ORD12345",
"timestamp": "2024-01-01T12:01:30Z",
"is_final": true,
"confidence": 0.95
},
"sequence_number": 5
}
}
function.called
Triggered when a function/tool is executed.
{
"event": "function.called",
"timestamp": "2024-01-01T12:01:35Z",
"data": {
"call_id": "call_abc123",
"function": {
"name": "get_order_status",
"parameters": {
"order_id": "ORD12345"
},
"result": {
"status": "shipped",
"delivery_date": "2024-01-03"
},
"duration_ms": 150
}
}
}
error.occurred
Triggered when an error occurs during the call.
{
"event": "error.occurred",
"timestamp": "2024-01-01T12:02:00Z",
"data": {
"call_id": "call_abc123",
"error": {
"code": "stt_timeout",
"message": "Speech-to-text service timed out",
"severity": "warning",
"recovered": true
}
}
}
Error Codes:
| Code | Severity | Description |
|---|---|---|
stt_timeout |
warning | STT service timeout |
stt_error |
error | STT service failure |
tts_error |
error | TTS service failure |
llm_timeout |
warning | LLM response timeout |
llm_error |
error | LLM service failure |
function_error |
warning | Tool execution failed |
connection_lost |
error | WebSocket disconnected |
dtmf.received
Triggered when DTMF input is received.
{
"event": "dtmf.received",
"timestamp": "2024-01-01T12:01:45Z",
"data": {
"call_id": "call_abc123",
"digits": "12345#",
"aggregated": true
}
}
Retry Policy
Failed webhook deliveries are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 second |
| 3 | 5 seconds |
| 4 | 30 seconds |
| 5 | 2 minutes |
Webhooks are considered failed if:
- Response status is not 2xx
- Connection timeout (10 seconds)
- Response timeout (30 seconds)
Best Practices
1. Respond Quickly
func handleWebhook(w http.ResponseWriter, r *http.Request) {
// Acknowledge immediately
w.WriteHeader(http.StatusOK)
// Process asynchronously
go processWebhookAsync(r.Body)
}
2. Idempotency
func processWebhook(payload WebhookPayload) error {
// Check if already processed
if db.WebhookProcessed(payload.CallID, payload.Event) {
return nil // Already handled
}
// Process and mark as done
err := handleEvent(payload)
if err == nil {
db.MarkWebhookProcessed(payload.CallID, payload.Event)
}
return err
}
3. Verify Signatures
Always verify webhook signatures in production to prevent spoofing.
Testing
Local Development
Use ngrok or similar to expose local endpoints:
ngrok http 3000
# Then configure webhook URL: https://abc123.ngrok.io/webhooks
Test Payload
curl -X POST https://your-server.com/webhooks \
-H "Content-Type: application/json" \
-H "X-Webhook-Event: call.ended" \
-d '{
"event": "call.ended",
"timestamp": "2024-01-01T12:00:00Z",
"data": {
"call_id": "test_123",
"duration_seconds": 60,
"end_reason": "user_hangup"
}
}'
Next Steps
- Webhooks Guide - Configuration and use cases
- REST API - API endpoints
- SDKs - Client libraries