Webhooks Overview
Webhooks are HTTP callbacks that Superfone sends to your server when specific call events occur. They enable real-time communication between Superfone and your application, allowing you to respond dynamically to call events.
How Webhooks Work
When a call event occurs (e.g., call answered, call ended), Superfone makes an HTTP request to your configured webhook URL with a JSON payload containing event details. Your server processes the payload and responds accordingly.
Webhook Types
SFVoPI supports three types of webhooks:
| Webhook | When Called | Response Required | Timeout |
|---|---|---|---|
| Answer Webhook | When call is answered (callee picks up) | Yes — must return Stream JSON | 10 seconds (with fallback) |
| Hangup Webhook | When call ends (any reason) | No — response ignored | 5 seconds (fire-and-forget) |
| Ring Webhook | When phone starts ringing (optional) | No — response ignored | 5 seconds (fire-and-forget) |
The Answer Webhook is the most critical webhook — it MUST return a valid Stream JSON response within 10 seconds, or the call will fail. The Hangup Webhook is fire-and-forget and does not require a response.
Configuring Webhooks
Webhooks are configured at two levels:
1. App-Level Webhooks (Default)
When you create a SFVoPI app, you specify default webhook URLs that apply to all calls handled by that app:
{
"name": "My Voice App",
"answer_url": "https://your-server.com/webhook/answer",
"answer_method": "POST",
"hangup_url": "https://your-server.com/webhook/hangup",
"hangup_method": "POST",
"fallback_answer_url": "https://backup-server.com/webhook/answer",
"fallback_answer_method": "POST"
}
See: Create App API
2. Call-Level Webhooks (Override)
When initiating an outbound call, you can override the app's default webhooks for that specific call:
{
"from": "+918000000003",
"to": "+918000000001",
"answer_url": "https://custom-server.com/webhook/answer",
"answer_method": "POST",
"ring_url": "https://custom-server.com/webhook/ring",
"ring_method": "POST",
"hangup_url": "https://custom-server.com/webhook/hangup",
"hangup_method": "POST"
}
See: Initiate Call API
HTTP Methods
Webhooks support two HTTP methods:
- POST (recommended): Payload sent in request body as JSON
- GET: Payload sent as URL query parameters
Use POST for webhooks to avoid URL length limits and ensure proper JSON encoding. GET is provided for compatibility with legacy systems.
Webhook Timeouts
| Webhook | Timeout | Fallback Behavior |
|---|---|---|
| Answer Webhook | 10 seconds | If primary answer_url fails or times out, Superfone tries fallback_answer_url (if configured) with 5-second timeout |
| Hangup Webhook | 5 seconds | Fire-and-forget — errors logged but do not affect call |
| Ring Webhook | 5 seconds | Fire-and-forget — errors logged but do not affect call |
If both answer_url and fallback_answer_url fail or time out, the call will be terminated with a failure status.
Webhook Security
HTTPS Required
All webhook URLs must use HTTPS in production. HTTP is only allowed for local development (e.g., http://localhost:3000).
IP Whitelisting (Recommended)
Restrict webhook endpoint access to Superfone's IP addresses:
# Superfone webhook source IPs (example)
203.0.113.10
203.0.113.11
203.0.113.12
Contact Superfone support for the current list of webhook source IPs.
Request Validation
Validate incoming webhook requests:
- Check Content-Type: Ensure
Content-Type: application/jsonfor POST requests - Validate Payload Schema: Use JSON schema validation to ensure payload structure matches expected format
- Check Required Fields: Verify all required fields are present (e.g.,
call_uuid,from,to)
Webhook signature verification (HMAC-based authentication) is not currently implemented but may be added in future releases.
Webhook Reliability
Idempotency
Webhooks are not guaranteed to be delivered exactly once. Your server should handle duplicate webhook deliveries gracefully:
- Use
call_uuidorrequest_uuidas idempotency key - Check if event already processed before taking action
- Store processed event IDs in database or cache
- JavaScript
- TypeScript
- Python
const processedEvents = new Set();
app.post('/webhook/hangup', (req, res) => {
const { call_uuid } = req.body;
// Check if already processed
if (processedEvents.has(call_uuid)) {
console.log(`Duplicate hangup webhook for call ${call_uuid}`);
return res.json({ received: true });
}
// Process event
processedEvents.add(call_uuid);
// ... handle hangup logic
res.json({ received: true });
});
const processedEvents = new Set<string>();
app.post('/webhook/hangup', (req: Request, res: Response) => {
const { call_uuid } = req.body as HangupWebhookPayload;
// Check if already processed
if (processedEvents.has(call_uuid)) {
console.log(`Duplicate hangup webhook for call ${call_uuid}`);
return res.json({ received: true });
}
// Process event
processedEvents.add(call_uuid);
// ... handle hangup logic
res.json({ received: true });
});
processed_events = set()
@app.route('/webhook/hangup', methods=['POST'])
def hangup_webhook():
payload = request.json
call_uuid = payload['call_uuid']
# Check if already processed
if call_uuid in processed_events:
print(f"Duplicate hangup webhook for call {call_uuid}")
return jsonify({"received": True})
# Process event
processed_events.add(call_uuid)
# ... handle hangup logic
return jsonify({"received": True})
Retry Logic
Superfone does not automatically retry failed webhook deliveries. If your server is temporarily unavailable:
- Answer Webhook: Superfone tries
fallback_answer_url(if configured), then fails the call - Hangup Webhook: Event is lost (fire-and-forget)
- Ring Webhook: Event is lost (fire-and-forget)
Ensure your webhook endpoints have high availability (99.9%+ uptime) and fast response times (<1 second).
Response Requirements
Answer Webhook
MUST return HTTP 200 with valid Stream JSON:
{
"stream": {
"url": "wss://your-server.com/ws",
"codec": "PCMU",
"sample_rate": 8000,
"direction": "BOTH",
"stream_timeout": 86400,
"extra_headers": { "X-Session-Id": "abc123" }
}
}
See: Answer Webhook
Hangup Webhook
Response is ignored — return any HTTP 2xx status to acknowledge receipt:
{
"received": true
}
See: Hangup Webhook
Ring Webhook
Response is ignored — return any HTTP 2xx status to acknowledge receipt:
{
"received": true
}
Error Handling
Answer Webhook Errors
If your answer webhook fails (timeout, non-200 status, invalid JSON), Superfone:
- Tries
fallback_answer_url(if configured) - If fallback also fails, terminates call with
FAILEDstatus - Sends hangup webhook with
call_status: "FAILED"
Hangup Webhook Errors
Errors are logged but do not affect call processing (fire-and-forget).
Common Error Scenarios
| Error | Cause | Solution |
|---|---|---|
| Timeout | Server response took >10s (answer) or >5s (hangup) | Optimize webhook handler, return response quickly |
| Connection refused | Server not reachable at webhook URL | Check firewall, DNS, server status |
| Invalid JSON | Response body is not valid JSON | Validate JSON before sending response |
| Schema validation failed | Stream JSON missing required fields | Ensure all required fields present (url, codec, sample_rate, direction) |
Testing Webhooks
Local Development with ngrok
Use ngrok to expose your local server for webhook testing:
# Start your server
npm run dev
# In another terminal, expose port 3000
ngrok http 3000
# Copy the HTTPS URL (e.g., https://abc123.ngrok.io)
# Use this URL in your SFVoPI app configuration
Webhook Testing Tools
- Postman: Simulate webhook requests with custom payloads
- RequestBin: Capture and inspect webhook requests
- ngrok Inspector: View webhook requests in real-time at http://localhost:4040
Example Test Payload
- cURL
- JavaScript
curl -X POST https://your-server.com/webhook/answer \
-H "Content-Type: application/json" \
-d '{
"call_uuid": "test-uuid-12345",
"request_uuid": "sfv_ob_req_test123",
"from": "+918000000003",
"to": "+918000000001",
"direction": "OUTBOUND",
"call_status": "IN_PROGRESS",
"answered_at": "2026-02-02T10:00:00.000Z"
}'
const response = await fetch('https://your-server.com/webhook/answer', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
call_uuid: 'test-uuid-12345',
request_uuid: 'sfv_ob_req_test123',
from: '+918000000003',
to: '+918000000001',
direction: 'OUTBOUND',
call_status: 'IN_PROGRESS',
answered_at: '2026-02-02T10:00:00.000Z'
})
});
const streamJson = await response.json();
console.log('Stream config:', streamJson);
Next Steps
- Answer Webhook — Handle call answered events and return Stream JSON
- Hangup Webhook — Handle call ended events and process call data
- Audio Streaming — Learn how to stream audio after answer webhook
- Initiate Call API — Make outbound calls with custom webhooks