Skip to main content

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.

Critical

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)

Important

The payload is FLAT — all fields are at the top level, including request_uuid for your convenience.

FieldTypeDescription
call_uuidstringUnique identifier for this call
request_uuidstringRequest UUID for outbound calls (format: sfv_ob_req_xxxxxxxxxxxx). Not present for inbound calls.
fromstringCaller's phone number (E.164 format, e.g., +918000000003)
tostringCallee's phone number (E.164 format, e.g., +918000000001)
directionstringCall direction: INBOUND or OUTBOUND
call_statusstringCurrent call status: IN_PROGRESS
answered_atstringISO 8601 timestamp when call was answered (e.g., 2026-02-02T10:00:00.000Z)

Example Payloads

{
"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"
}

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

FieldTypeRequiredDefaultDescriptionValidation
urlstringYesWebSocket URL where Superfone will stream audio (must start with wss:// or ws://)Valid WebSocket URL
codecstringYesPCMUAudio codec: PCMU (G.711 μ-law) or PCMA (G.711 A-law)Enum: PCMU, PCMA
sample_ratenumberYes8000Audio sample rate in Hz: 8000 or 16000Enum: 8000, 16000
directionstringYesBOTHAudio stream direction: INBOUND (from callee), OUTBOUND (to callee), or BOTH (bidirectional)Enum: INBOUND, OUTBOUND, BOTH
bidirectionalbooleanNotrueWhether audio flows in both directions (deprecated — use direction instead)Boolean
stream_timeoutnumberNo86400Maximum stream duration in seconds (1-86400, i.e., 1 second to 24 hours)Min: 1, Max: 86400
keep_call_alivebooleanNotrueWhether to keep call active after stream endsBoolean
extra_headersobjectNo{}Custom HTTP headers to send in WebSocket handshake requestKey-value pairs (string keys, string values)
tip

Use extra_headers to pass session identifiers, authentication tokens, or other metadata to your WebSocket server.

Webhook Handler Examples

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');
});

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

ScenarioBehavior
Primary succeedsCall proceeds with Stream JSON from primary
Primary fails, fallback succeedsCall proceeds with Stream JSON from fallback
Both failCall terminated, hangup webhook sent with call_status: "FAILED"
tip

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 CodeMeaningSuperfone Action
200Success — Stream JSON acceptedProceed with audio streaming
400Bad Request — Invalid Stream JSONTry fallback, then fail call
500Internal Server ErrorTry fallback, then fail call
TimeoutNo response within 10 secondsTry 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

  1. Response Time: Respond as quickly as possible (ideally <1 second) to minimize call setup delay
  2. WebSocket Availability: Ensure your WebSocket server is running and reachable before returning the Stream JSON
  3. HTTPS Required: Use wss:// (secure WebSocket) in production; ws:// is only for local development
  4. Idempotency: Use call_uuid to track processed calls and avoid duplicate processing
  5. Logging: Log all webhook requests for debugging and audit purposes
  6. Error Handling: Return HTTP 200 even if you encounter internal errors — log the error and return a fallback Stream JSON

Next Steps