Skip to main content

Hangup Webhook

The Hangup Webhook is called when a call ends, regardless of the reason (completed, failed, canceled, etc.). This webhook provides call detail records (CDR) including duration, timestamps, and call status.

Fire-and-Forget

This webhook is fire-and-forget — Superfone does not wait for your response or retry on failure. The response body is ignored. Use this webhook to log call data, update databases, or trigger post-call workflows.

When It's Called

The hangup webhook is triggered when:

  • Call completes normally (both parties hang up)
  • Callee doesn't answer (ring timeout)
  • Callee is busy
  • Call is canceled before being answered
  • Call fails due to network issues or other errors

Webhook Payload

Superfone sends a POST or GET request (based on your app configuration) with the following payload:

Payload Structure

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_statusstringFinal call status: COMPLETED, NO_ANSWER, BUSY, CANCELED, or FAILED
durationnumberCall duration in seconds (time from answer to hangup). 0 if call was not answered.
started_atstringISO 8601 timestamp when call started ringing (e.g., 2026-02-02T10:00:00.000Z)
answer_atstringISO 8601 timestamp when call was answered. undefined if call was not answered.
ended_atstringISO 8601 timestamp when call ended (e.g., 2026-02-02T10:05:00.000Z)
hangup_causestringHangup cause code (e.g., 16 for normal clearing). Optional.

Call Status Values

The call_status field indicates why the call ended:

StatusDescriptionWhen It Occurs
COMPLETEDCall answered and hung up normallyBoth parties connected, call ended normally
NO_ANSWERCallee didn't answerRing timeout reached, callee didn't pick up
BUSYCallee was busyCallee's line was busy
CANCELEDCall canceled before being answeredCaller hung up before callee answered
FAILEDCall failedNetwork issue, invalid number, unreachable, or other error
Call Status Mapping

Superfone maps call outcomes to these statuses:

  • ANSWER, BACKUP_ANSWER, IVR_ANSWEREDCOMPLETED
  • NOANSWER, BACKUP_NOANSWERNO_ANSWER
  • BUSY, BACKUP_BUSYBUSY
  • CANCEL, BACKUP_CANCELCANCELED
  • CONGESTION, CHANUNAVAIL, REJECTED, BLOCKEDFAILED

Example Payloads

{
"call_uuid": "call-uuid-1738491600-abc123",
"request_uuid": "sfv_ob_req_a8k3m2x9p1z0",
"from": "+918000000003",
"to": "+918000000001",
"direction": "OUTBOUND",
"call_status": "COMPLETED",
"duration": 120,
"started_at": "2026-02-02T10:00:00.000Z",
"answer_at": "2026-02-02T10:00:10.000Z",
"ended_at": "2026-02-02T10:02:10.000Z",
"hangup_cause": "16"
}

Notes:

  • duration: 120 seconds (2 minutes)
  • answer_at: Call was answered 10 seconds after ringing started
  • hangup_cause: 16 = Normal call clearing

Response Handling

Your webhook response is ignored by Superfone. You can return any HTTP 2xx status to acknowledge receipt:

{
"received": true
}
tip

Return a response quickly (ideally <100ms). Perform long-running tasks (database writes, API calls) asynchronously after responding.

Webhook Handler Examples

const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook/hangup', (req, res) => {
const payload = req.body;

console.log('[Hangup Webhook] Call ended:', {
call_uuid: payload.call_uuid,
request_uuid: payload.request_uuid,
from: payload.from,
to: payload.to,
direction: payload.direction,
call_status: payload.call_status,
duration: payload.duration,
started_at: payload.started_at,
answer_at: payload.answer_at,
ended_at: payload.ended_at,
hangup_cause: payload.hangup_cause
});

// Respond immediately
res.json({ received: true });

// Process asynchronously (don't block response)
processCallData(payload).catch(err => {
console.error('Error processing call data:', err);
});
});

async function processCallData(payload) {
// Save to database
await db.calls.insert({
call_uuid: payload.call_uuid,
from: payload.from,
to: payload.to,
status: payload.call_status,
duration: payload.duration,
started_at: new Date(payload.started_at),
ended_at: new Date(payload.ended_at)
});

// Trigger post-call workflow
if (payload.call_status === 'COMPLETED' && payload.duration > 60) {
await sendFollowUpEmail(payload.from, payload.to);
}
}

app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});

Timeout

Superfone waits 5 seconds for your webhook to respond. If your server doesn't respond within this time, the request is considered failed and logged, but this does not affect call processing (fire-and-forget).

warning

Do not perform long-running operations (database writes, external API calls) before responding. Respond immediately and process data asynchronously.

Use Cases

1. Call Analytics

Track call metrics and generate reports:

app.post('/webhook/hangup', async (req, res) => {
const { call_status, duration, direction } = req.body;

// Respond immediately
res.json({ received: true });

// Update analytics asynchronously
await analytics.track({
event: 'call_ended',
properties: {
status: call_status,
duration,
direction,
timestamp: new Date()
}
});
});

2. CRM Integration

Update customer records with call data:

app.post('/webhook/hangup', async (req, res) => {
const { from, to, call_status, duration } = req.body;

// Respond immediately
res.json({ received: true });

// Update CRM asynchronously
if (call_status === 'COMPLETED') {
await crm.updateContact(to, {
last_call_date: new Date(),
last_call_duration: duration,
last_call_status: call_status
});
}
});

3. Missed Call Notifications

Send notifications for missed calls:

app.post('/webhook/hangup', async (req, res) => {
const { call_status, from, to } = req.body;

// Respond immediately
res.json({ received: true });

// Send notification asynchronously
if (call_status === 'NO_ANSWER') {
await sendSMS(to, `You missed a call from ${from}`);
}
});

4. Billing and Usage Tracking

Calculate call costs and update billing:

app.post('/webhook/hangup', async (req, res) => {
const { call_uuid, duration, direction } = req.body;

// Respond immediately
res.json({ received: true });

// Calculate cost asynchronously
const costPerMinute = direction === 'OUTBOUND' ? 0.05 : 0.02;
const cost = (duration / 60) * costPerMinute;

await billing.recordCharge({
call_uuid,
duration,
cost,
timestamp: new Date()
});
});

Notes

  1. Fire-and-Forget: Response is ignored — return quickly and process asynchronously
  2. No Retries: Superfone does not retry failed hangup webhooks
  3. Idempotency: Use call_uuid to avoid duplicate processing if webhook is called multiple times
  4. Duration Calculation: duration is time from answer_at to ended_at. If call was not answered, duration is 0.
  5. Timestamp Format: All timestamps are ISO 8601 strings (e.g., 2026-02-02T10:00:00.000Z)
  6. Hangup Cause Codes: See the Common Hangup Cause Codes table below for hangup_cause meanings

Common Hangup Cause Codes

CodeMeaningTypical Scenario
16Normal call clearingCall completed normally
17User busyCallee's line was busy
19No answerRing timeout, callee didn't answer
21Call rejectedCallee rejected the call
487Request terminatedCaller canceled before answer
503Service unavailableNetwork issue, server unreachable

Next Steps

  • Track call metrics and generate reports
  • Update customer records with call data
  • Calculate call costs and update billing