Answer Webhook
The Answer Webhook is called when a call is answered (callee picks up the phone). Your server must respond with a Stream JSON configuration that tells Superfone where to stream the call's audio.
This webhook MUST return a valid Stream JSON response within 10 seconds, or the call will fail. If the primary answer_url fails, Superfone tries the fallback_answer_url (if configured).
When It's Called
The answer webhook is triggered when:
- Inbound calls: A caller dials a number linked to your SFVoPI app, and the call is answered
- Outbound calls: You initiate a call via the API, and the callee answers
Webhook Payload
Superfone sends a POST or GET request (based on your app configuration) with the following payload:
Payload Structure (FLAT)
The payload is FLAT — all fields are at the top level, including request_uuid for your convenience.
| Field | Type | Description |
|---|---|---|
call_uuid | string | Unique identifier for this call |
request_uuid | string | Request UUID for outbound calls (format: sfv_ob_req_xxxxxxxxxxxx). Not present for inbound calls. |
from | string | Caller's phone number (E.164 format, e.g., +918000000003) |
to | string | Callee's phone number (E.164 format, e.g., +918000000001) |
direction | string | Call direction: INBOUND or OUTBOUND |
call_status | string | Current call status: IN_PROGRESS |
answered_at | string | ISO 8601 timestamp when call was answered (e.g., 2026-02-02T10:00:00.000Z) |
Example Payloads
- Outbound Call
- Inbound Call
{
"call_uuid": "call-uuid-1738491600-abc123",
"request_uuid": "sfv_ob_req_a8k3m2x9p1z0",
"from": "+918000000003",
"to": "+918000000001",
"direction": "OUTBOUND",
"call_status": "IN_PROGRESS",
"answered_at": "2026-02-02T10:00:00.000Z"
}
{
"call_uuid": "call-uuid-1738491600-def456",
"from": "+918000000001",
"to": "+918000000003",
"direction": "INBOUND",
"call_status": "IN_PROGRESS",
"answered_at": "2026-02-02T10:00:00.000Z"
}
Inbound calls do not include request_uuid because they are not initiated via the API.
Required Response: Stream JSON
Your server must respond with HTTP 200 and a JSON body containing the stream object:
Response Schema
{
"stream": {
"url": "wss://your-server.com/ws",
"codec": "PCMU",
"sample_rate": 8000,
"direction": "BOTH",
"bidirectional": true,
"stream_timeout": 86400,
"keep_call_alive": true,
"extra_headers": {
"X-Session-Id": "abc123",
"X-Custom-Header": "value"
}
}
}
Stream Object Fields
| Field | Type | Required | Default | Description | Validation |
|---|---|---|---|---|---|
url | string | Yes | — | WebSocket URL where Superfone will stream audio (must start with wss:// or ws://) | Valid WebSocket URL |
codec | string | Yes | PCMU | Audio codec: PCMU (G.711 μ-law) or PCMA (G.711 A-law) | Enum: PCMU, PCMA |
sample_rate | number | Yes | 8000 | Audio sample rate in Hz: 8000 or 16000 | Enum: 8000, 16000 |
direction | string | Yes | BOTH | Audio stream direction: INBOUND (from callee), OUTBOUND (to callee), or BOTH (bidirectional) | Enum: INBOUND, OUTBOUND, BOTH |
bidirectional | boolean | No | true | Whether audio flows in both directions (deprecated — use direction instead) | Boolean |
stream_timeout | number | No | 86400 | Maximum stream duration in seconds (1-86400, i.e., 1 second to 24 hours) | Min: 1, Max: 86400 |
keep_call_alive | boolean | No | true | Whether to keep call active after stream ends | Boolean |
extra_headers | object | No | {} | Custom HTTP headers to send in WebSocket handshake request | Key-value pairs (string keys, string values) |
Use extra_headers to pass session identifiers, authentication tokens, or other metadata to your WebSocket server.
Webhook Handler Examples
- JavaScript
- TypeScript
- Python
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook/answer', (req, res) => {
const payload = req.body;
console.log('[Answer Webhook] Call answered:', {
call_uuid: payload.call_uuid,
request_uuid: payload.request_uuid,
from: payload.from,
to: payload.to,
direction: payload.direction,
answered_at: payload.answered_at
});
// Return Stream JSON
const streamConfig = {
stream: {
url: 'wss://your-server.com/ws',
codec: 'PCMU',
sample_rate: 8000,
direction: 'BOTH',
stream_timeout: 86400,
extra_headers: {
'X-Call-UUID': payload.call_uuid,
'X-Request-UUID': payload.request_uuid || 'N/A'
}
}
};
res.json(streamConfig);
});
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
import express, { Request, Response } from 'express';
interface AnswerWebhookPayload {
call_uuid: string;
request_uuid?: string;
from: string;
to: string;
direction: 'INBOUND' | 'OUTBOUND';
call_status: 'IN_PROGRESS';
answered_at: string;
}
interface StreamResponse {
stream: {
url: string;
codec: 'PCMU' | 'PCMA';
sample_rate: 8000 | 16000;
direction: 'INBOUND' | 'OUTBOUND' | 'BOTH';
bidirectional?: boolean;
stream_timeout?: number;
keep_call_alive?: boolean;
extra_headers?: Record<string, string>;
};
}
const app = express();
app.use(express.json());
app.post('/webhook/answer', (req: Request, res: Response) => {
const payload = req.body as AnswerWebhookPayload;
console.log('[Answer Webhook] Call answered:', {
call_uuid: payload.call_uuid,
request_uuid: payload.request_uuid,
from: payload.from,
to: payload.to,
direction: payload.direction,
answered_at: payload.answered_at
});
// Return Stream JSON
const streamConfig: StreamResponse = {
stream: {
url: 'wss://your-server.com/ws',
codec: 'PCMU',
sample_rate: 8000,
direction: 'BOTH',
stream_timeout: 86400,
extra_headers: {
'X-Call-UUID': payload.call_uuid,
'X-Request-UUID': payload.request_uuid || 'N/A'
}
}
};
res.json(streamConfig);
});
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhook/answer', methods=['POST'])
def answer_webhook():
payload = request.json
print('[Answer Webhook] Call answered:', {
'call_uuid': payload['call_uuid'],
'request_uuid': payload.get('request_uuid', 'N/A'),
'from': payload['from'],
'to': payload['to'],
'direction': payload['direction'],
'answered_at': payload['answered_at']
})
# Return Stream JSON
stream_config = {
'stream': {
'url': 'wss://your-server.com/ws',
'codec': 'PCMU',
'sample_rate': 8000,
'direction': 'BOTH',
'stream_timeout': 86400,
'extra_headers': {
'X-Call-UUID': payload['call_uuid'],
'X-Request-UUID': payload.get('request_uuid', 'N/A')
}
}
}
return jsonify(stream_config)
if __name__ == '__main__':
app.run(port=3000)
Timeout and Fallback
Primary Answer URL
Superfone waits 10 seconds for your answer_url to respond. If it fails (timeout, connection error, non-200 status, invalid JSON), Superfone tries the fallback_answer_url (if configured).
Fallback Answer URL
If the primary answer_url fails, Superfone tries the fallback_answer_url with a 5-second timeout. If the fallback also fails, the call is terminated with FAILED status.
Failure Scenarios
| Scenario | Behavior |
|---|---|
| Primary succeeds | Call proceeds with Stream JSON from primary |
| Primary fails, fallback succeeds | Call proceeds with Stream JSON from fallback |
| Both fail | Call terminated, hangup webhook sent with call_status: "FAILED" |
Configure a fallback_answer_url pointing to a backup server or a static WebSocket endpoint to ensure high availability.
Error Responses
If your webhook returns an error, Superfone logs it and tries the fallback (if configured):
| Status Code | Meaning | Superfone Action |
|---|---|---|
| 200 | Success — Stream JSON accepted | Proceed with audio streaming |
| 400 | Bad Request — Invalid Stream JSON | Try fallback, then fail call |
| 500 | Internal Server Error | Try fallback, then fail call |
| Timeout | No response within 10 seconds | Try fallback, then fail call |
Invalid Stream JSON Examples
// Missing required field 'url'
{
"stream": {
"codec": "PCMU",
"sample_rate": 8000,
"direction": "BOTH"
}
}
// Invalid codec value
{
"stream": {
"url": "wss://your-server.com/ws",
"codec": "OPUS",
"sample_rate": 8000,
"direction": "BOTH"
}
}
// Invalid sample_rate value
{
"stream": {
"url": "wss://your-server.com/ws",
"codec": "PCMU",
"sample_rate": 48000,
"direction": "BOTH"
}
}
Use Cases
1. Dynamic WebSocket Routing
Route calls to different WebSocket servers based on call metadata:
app.post('/webhook/answer', (req, res) => {
const { from, to, direction } = req.body;
// Route to different servers based on direction
const wsUrl = direction === 'INBOUND'
? 'wss://inbound-server.com/ws'
: 'wss://outbound-server.com/ws';
res.json({
stream: {
url: wsUrl,
codec: 'PCMU',
sample_rate: 8000,
direction: 'BOTH'
}
});
});
2. Session Tracking with Extra Headers
Pass session identifiers to your WebSocket server:
app.post('/webhook/answer', (req, res) => {
const { call_uuid, request_uuid } = req.body;
// Generate session ID
const sessionId = `session_${Date.now()}_${call_uuid}`;
res.json({
stream: {
url: 'wss://your-server.com/ws',
codec: 'PCMU',
sample_rate: 8000,
direction: 'BOTH',
extra_headers: {
'X-Session-Id': sessionId,
'X-Call-UUID': call_uuid,
'X-Request-UUID': request_uuid || 'N/A'
}
}
});
});
3. Call Time Limits
Set a custom stream timeout based on call type:
app.post('/webhook/answer', (req, res) => {
const { direction } = req.body;
// Shorter timeout for outbound calls
const timeout = direction === 'OUTBOUND' ? 300 : 3600;
res.json({
stream: {
url: 'wss://your-server.com/ws',
codec: 'PCMU',
sample_rate: 8000,
direction: 'BOTH',
stream_timeout: timeout
}
});
});
Notes
- Response Time: Respond as quickly as possible (ideally <1 second) to minimize call setup delay
- WebSocket Availability: Ensure your WebSocket server is running and reachable before returning the Stream JSON
- HTTPS Required: Use
wss://(secure WebSocket) in production;ws://is only for local development - Idempotency: Use
call_uuidto track processed calls and avoid duplicate processing - Logging: Log all webhook requests for debugging and audit purposes
- Error Handling: Return HTTP 200 even if you encounter internal errors — log the error and return a fallback Stream JSON
Related Endpoints
- Hangup Webhook — Handle call ended events
- Audio Streaming Overview — Learn how to stream audio via WebSocket
- WebSocket Protocol — Understand WebSocket message format
- Create App API — Configure answer webhook URL
- Initiate Call API — Override answer webhook per call
Next Steps
- Implement WebSocket Server — Handle audio streaming after answer webhook
- Hangup Webhook — Process call end events and CDR data
- Handle WebSocket disconnections and errors