Skip to main content
Webhooks let your application receive signed inbound HTTP requests from Voice.ai and expose outbound callable endpoints for agent actions.
Prerequisites: API key and an agent configured with webhook settings.

Overview

Voice.ai supports three webhook types. webhooks.events, webhooks.inbound_call, and webhooks.tools use different contracts:
  • webhooks.events supports secret (write-only on create/update) and has_secret (read-only on fetch).
  • webhooks.inbound_call supports secret (write-only on create/update) and has_secret (read-only on fetch).
  • webhooks.tools define outbound API calls and do not use secret.
See Agent Configuration, the Web SDK guide, and the API reference for the same public contract on agent config and call-start requests.
TypeDirectionPurposeAuth
Event WebhooksInbound: Voice.ai → your URLNotify your system about call lifecycle eventsUse secret for HMAC signature verification
Inbound Call WebhookInbound: Voice.ai → your URLPersonalize inbound calls with dynamic_variablesUse secret for HMAC signature verification
Webhook ToolsOutbound: Voice.ai calls your APILet the agent invoke your application during a callUse auth_type/auth_token/headers for request authentication
EventDescription
call.startedCall has connected and the agent is ready
call.completedCall has ended, includes transcript and usage data
When configured, your server receives an HTTP POST request for each event with a JSON payload containing event details.

Event Webhooks

Configuration

Add event webhook configuration to your agent’s config.webhooks.events object:
{
  "name": "My Agent",
  "config": {
    "prompt": "You are a helpful assistant.",
    "webhooks": {
      "events": {
        "url": "https://your-server.com/webhooks/voice-events",
        "secret": "your-signing-secret",
        "events": ["call.started", "call.completed"],
        "timeout": 5,
        "enabled": true
      }
    }
  }
}

Event configuration parameters

events supports required/optional fields: required url; optional secret, events, timeout (default 5), enabled (default true). Omit optional fields to use defaults.
ParameterTypeRequiredDefaultDescription
urlstringYes-Your webhook endpoint URL (must be HTTPS in production)
secretstringNonullHMAC-SHA256 signing secret for payload verification
has_secretbooleanNofalseWhether a signing secret is configured (read-only on fetch)
eventsarrayNo[]Event types to receive. Empty array = all events
timeoutnumberNo5Request timeout in seconds (1-30)
enabledbooleanNotrueWhether webhook notifications are active

Event Payloads

All webhook events share a common structure with top-level fields and event-specific data:
FieldTypeDescription
eventstringEvent type (call.started, call.completed)
timestampstringISO 8601 timestamp of when the event occurred
call_idstringUnique identifier for the call
agent_idstringAgent handling the call
dataobjectEvent-specific additional data

call.started

Sent when a call connects and the agent is ready to interact.
{
  "event": "call.started",
  "timestamp": "2025-02-03T14:30:00.000Z",
  "call_id": "call_abc123",
  "agent_id": "agent_456",
  "data": {
    "call_type": "sip_inbound",
    "started_at": "2025-02-03T14:30:00.000Z",
    "from_number": "+14155551234",
    "to_number": "+14155559876"
  }
}
data fields:
FieldTypeDescription
call_typestring"web", "sip_inbound", or "sip_outbound"
started_atstringISO 8601 timestamp
from_numberstringCaller’s phone number (SIP calls only)
to_numberstringCalled phone number (SIP calls only)
from_number and to_number are only included for SIP (phone) calls. Web calls do not have these fields.

call.completed

Sent after the call ends and all data has been processed. Includes transcript and usage information.
{
  "event": "call.completed",
  "timestamp": "2025-02-03T14:35:00.000Z",
  "call_id": "call_abc123",
  "agent_id": "agent_456",
  "data": {
    "call_type": "sip_inbound",
    "duration_seconds": 300.5,
    "credits_used": 15.25,
    "transcript_uri": "https://storage.example.com/transcripts/call_abc123.json",
    "transcript_summary": "Customer inquired about pricing for the enterprise plan...",
    "from_number": "+14155551234",
    "to_number": "+14155559876"
  }
}
data fields:
FieldTypeDescription
call_typestring"web", "sip_inbound", or "sip_outbound"
duration_secondsnumberCall duration in seconds
credits_usednumberCredits consumed by this call
transcript_uristringURL to full transcript JSON
transcript_summarystringAI-generated summary of the call
from_numberstringCaller’s phone number (SIP calls only)
to_numberstringCalled phone number (SIP calls only)

Inbound Call Webhook

webhooks.inbound_call runs before an inbound phone call starts. Use it to personalize a call with dynamic_variables. Do not use it to route a call to a different agent.

Configuration

Add inbound call webhook configuration to your agent’s config.webhooks.inbound_call object:
{
  "name": "My Agent",
  "config": {
    "prompt": "You are a helpful assistant.",
    "webhooks": {
      "inbound_call": {
        "url": "https://your-server.com/webhooks/inbound-call",
        "secret": "your-signing-secret",
        "timeout": 5,
        "enabled": true
      }
    }
  }
}

Inbound call configuration parameters

ParameterTypeRequiredDefaultDescription
urlstringYes-Your inbound call webhook endpoint URL
secretstringNonullHMAC-SHA256 signing secret for payload verification
has_secretbooleanNofalseWhether a signing secret is configured (read-only on fetch)
timeoutnumberNo5Request timeout in seconds (1-30)
enabledbooleanNotrueWhether inbound call personalization is active

Request payload

Voice.ai sends a POST request with the inbound call context:
{
  "agent_id": "agent_456",
  "call_id": "call_abc123",
  "from_number": "+14155551234",
  "to_number": "+14155559876"
}
FieldTypeDescription
agent_idstringAgent receiving the inbound call
call_idstringUnique identifier for the call
from_numberstringCaller’s phone number
to_numberstringNumber the caller dialed

Response payload

Your endpoint can return dynamic_variables:
{
  "dynamic_variables": {
    "customer_name": "Alice",
    "vip": true
  }
}
FieldTypeDescription
dynamic_variablesobjectOptional flat object of string, number, or boolean values
Omitted fields are allowed. Unused dynamic_variables are ignored by the runtime. See the Web SDK guide for browser-side examples.

Webhook Tools

Tools are outbound API calls: Voice.ai calls your endpoint. Use auth_type/auth_token/headers (not secret - that is only for signed inbound webhooks like events and inbound_call). Use config.webhooks.tools to declare callable functions:
{
  "name": "My Agent",
  "config": {
    "prompt": "You are a helpful assistant.",
    "webhooks": {
      "events": {
        "url": "https://your-server.com/webhooks/voice-events",
        "secret": "your-signing-secret",
        "events": ["call.started", "call.completed"]
      },
      "tools": [
        {
          "name": "get_account_status",
          "description": "Fetches the latest account status for a customer.",
          "url": "https://your-server.com/webhooks/tools/account-status",
          "parameters": { "customer_id": "string" },
          "method": "POST",
          "execution_mode": "sync",
          "auth_type": "api_key",
          "auth_token": "your-api-key",
          "headers": { "X-Service-Version": "2026-02" },
          "response": {
            "type": "object",
            "properties": {
              "status": { "type": "string" },
              "tier": { "type": "string" }
            }
          },
          "timeout": 10
        },
        {
          "name": "search_knowledge_base",
          "description": "Searches the internal KB and returns ranked snippets.",
          "url": "https://your-server.com/webhooks/tools/search-kb",
          "parameters": {
            "query": "string",
            "top_k": "number"
          },
          "method": "GET",
          "execution_mode": "async",
          "auth_type": "custom_headers",
          "headers": {
            "X-Internal-Token": "your-internal-token"
          },
          "timeout": 10
        }
      ]
    }
  }
}

Tool configuration parameters

tools[] supports required/optional fields per tool: required name, description, parameters, url, method, execution_mode, auth_type; optional auth_token, headers, response, timeout (default 10). Omit optional fields to use defaults.
ParameterTypeRequiredDefaultDescription
namestringYes-Tool name used in function_name
descriptionstringYes-Human-readable tool behavior description
urlstringYes-Your API endpoint URL
parametersobjectYes-Tool argument schema
methodstringYes-GET, POST, PUT, PATCH, or DELETE
execution_modestringYes-sync (wait for response) or async (accept 2xx)
auth_typestringYes-none, bearer_token, api_key, or custom_headers
auth_tokenstringNonullToken for bearer_token or api_key auth
headersobjectNo{}Custom headers (for auth_type: custom_headers)
responseobjectNo{}Expected response shape
timeoutnumberNo10Request timeout in seconds

Tool request shape

Voice.ai makes HTTP requests directly to your tool URL:
  • GET: Arguments as query parameters
  • POST/PUT/PATCH/DELETE: Arguments as JSON body
Metadata is sent in headers: X-VoiceAI-Request-Id, X-VoiceAI-Tool-Name, X-VoiceAI-Agent-Id, X-VoiceAI-Call-Id Example GET request:
GET /webhooks/tools/search-kb?query=refund+policy&top_k=3
X-VoiceAI-Request-Id: req_123
X-VoiceAI-Tool-Name: search_knowledge_base
X-VoiceAI-Agent-Id: agent_123
X-VoiceAI-Call-Id: call_123
Example POST request:
POST /webhooks/tools/account-status
Content-Type: application/json
X-VoiceAI-Request-Id: req_456
X-VoiceAI-Tool-Name: get_account_status
X-VoiceAI-Agent-Id: agent_123
X-VoiceAI-Call-Id: call_123

{"customer_id":"cust_987"}
Recommended response (sync mode):
{
  "result": {
    "status": "active",
    "tier": "enterprise"
  }
}

Tool authentication

  • auth_type: 'none': no auth headers added.
  • auth_type: 'bearer_token': sends Authorization: Bearer <auth_token>.
  • auth_type: 'api_key': sends X-API-Key: <auth_token>.
  • auth_type: 'custom_headers': sends your configured headers map.

Tool response behavior

  • execution_mode: 'sync': waits for downstream response body; non-2xx fails the tool call.
  • execution_mode: 'async': treats any 2xx as accepted and does not require a response payload.

Signed Inbound Webhook Headers

webhooks.events and webhooks.inbound_call requests include:
HeaderDescription
Content-Typeapplication/json
X-Webhook-TimestampUnix timestamp of when the request was signed
X-Webhook-SignatureHMAC-SHA256 signature (only if events.secret or inbound_call.secret is configured)

Signature Verification (Event and Inbound Call Webhooks)

webhooks.events and webhooks.inbound_call use secret for HMAC-SHA256. If you configure either secret, verify signatures to ensure requests are from Voice.ai. Tool webhooks use auth_type/auth_token/headers instead and do not use HMAC.

Signature Format

The signature is computed as:
HMAC-SHA256(secret, "{timestamp}.{payload}")
Where:
  • timestamp is the value from X-Webhook-Timestamp header
  • payload is the raw JSON request body

Verification Examples

Use the same verifier for both event webhooks and inbound call webhooks.
import hmac
import hashlib
import time

def verify_webhook(request_body: bytes, headers: dict, secret: str) -> bool:
    """Verify webhook signature and timestamp."""
    signature = headers.get('X-Webhook-Signature')
    timestamp = headers.get('X-Webhook-Timestamp')
    
    if not signature or not timestamp:
        return False
    
    # Reject requests older than 5 minutes (replay attack prevention)
    if abs(time.time() - int(timestamp)) > 300:
        return False
    
    # Compute expected signature
    message = f"{timestamp}.{request_body.decode()}"
    expected = hmac.new(
        secret.encode(),
        message.encode(),
        hashlib.sha256
    ).hexdigest()
    
    # Constant-time comparison
    return hmac.compare_digest(expected, signature)


# Flask example
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = "your-signing-secret"

@app.route('/webhooks/voice-events', methods=['POST'])
def handle_webhook():
    if not verify_webhook(request.data, request.headers, WEBHOOK_SECRET):
        return jsonify({"error": "Invalid signature"}), 401
    
    event = request.json
    event_type = event.get('event')
    
    if event_type == 'call.started':
        print(f"Call started: {event['call_id']}")
    elif event_type == 'call.completed':
        print(f"Call completed: {event['call_id']}, duration: {event['data']['duration_seconds']}s")
    
    return jsonify({"status": "ok"}), 200

Retry Logic

Failed webhook deliveries are retried with exponential backoff:
AttemptDelay
1Immediate
21 second
32 seconds
44 seconds
58 seconds
Retries stop after 5 attempts or on receiving a 4xx response (except 429 rate limit).
Idempotency: Your webhook handler should be idempotent. The same event may be delivered multiple times due to retries. Use call_id to deduplicate events.

Best Practices

  1. Always verify signatures in production to prevent spoofed requests
  2. Respond quickly with a 2xx status code within 5 seconds to avoid retries
  3. Process asynchronously - queue events for processing rather than blocking the response
  4. Handle duplicates - use call_id to deduplicate in case of retries
  5. Check timestamps - reject signed requests older than 5 minutes to prevent replay attacks
  6. Use HTTPS - ensure your webhook endpoint uses TLS encryption

Filtering Events

You can filter which events you receive by specifying the events array:
{
  "webhooks": {
    "events": {
      "url": "https://your-server.com/webhooks",
      "events": ["call.completed"]
    }
  }
}
This configuration only receives call.completed events. Leave events empty or omit it to receive all event types.

Testing Webhooks

You can test your webhook endpoint using the Test Webhook endpoint:
curl -X POST "https://api.voice.ai/api/v1/agent/{agent_id}/webhook/test" \
  -H "Authorization: Bearer YOUR_API_KEY"
This sends a test event to your configured webhook URL and returns the delivery result.

Testing Webhook Tools

You can test an individual webhook tool using the test tool endpoint:
curl -X POST "https://api.voice.ai/api/v1/agent/{agent_id}/webhook/test-tool/get_account_status" \
  -H "Authorization: Bearer YOUR_API_KEY"
This sends a sample function_call payload for the specified tool and returns the delivery result. For local development, use a tunnel service like ngrok to expose your local server:
ngrok http 3000
# Use the ngrok URL as your webhook URL during development

Next Steps

  • Agent Configuration - Configure prompts, dynamic_variables, and webhook settings
  • Web SDK - Pass dynamicVariables from the browser and configure webhooks through the SDK
  • Analytics - View call history and metrics
  • API Reference - Complete API documentation