Webhooks Overview
Webhooks allow your application to receive real-time HTTP notifications when events occur in your Firetell workspace.
How Webhooks Work
- You register a webhook URL via the Webhooks API or the Firetell Console
- When an event occurs, Firetell sends an HTTP
POSTrequest to your URL - Your server processes the event and responds with a
200status code
Webhook Payload Format
All webhook payloads follow a standard envelope structure:
{
"event": "call.ended",
"data": {
"workspace_id": "yourcompany",
"occurred_at": "2026-07-24T00:56:02.000Z",
"attempt": 1,
"call_id": "call_abc789",
"direction": "inbound",
"status": "completed",
"type": "audio",
"from": {
"name": "+84901234567",
"number": "+84901234567"
},
"to": {
"name": "+84909876543",
"number": "+84909876543"
},
"started_at": "2026-07-24T00:55:00.000Z",
"answered_at": "2026-07-24T00:55:02.000Z",
"ended_at": "2026-07-24T00:56:02.000Z",
"duration": 62,
"billsec": 60,
"cost": 0.0517,
"hangup_cause": "NORMAL_CLEARING"
}
}
Event Payload Examples
call.created
{
"event": "call.created",
"data": {
"workspace_id": "yourcompany",
"occurred_at": "2026-07-24T00:55:00.000Z",
"attempt": 1,
"call_id": "call_abc789",
"direction": "inbound",
"status": "started",
"type": "audio",
"from": { "name": "+84901234567", "number": "+84901234567" },
"to": { "name": "+84909876543", "number": "+84909876543" },
"started_at": "2026-07-24T00:55:00.000Z",
"answered_at": null,
"ended_at": null,
"duration": 0,
"billsec": 0,
"cost": 0,
"hangup_cause": null
}
}
call.recording.ready
{
"event": "call.recording.ready",
"data": {
"workspace_id": "yourcompany",
"occurred_at": "2026-07-24T00:56:05.000Z",
"attempt": 1,
"recording_id": "rec_call_abc789",
"call_id": "call_abc789",
"url": "https://storage.firetell.app/workspaces/yourcompany/recordings/call_abc789.mp3",
"duration": 60,
"size_bytes": 1440000,
"format": "mp3",
"created_at": "2026-07-24T00:56:05.000Z"
}
}
call.transcription.completed
{
"event": "call.transcription.completed",
"data": {
"workspace_id": "yourcompany",
"occurred_at": "2026-07-24T00:56:06.000Z",
"attempt": 1,
"call_id": "call_abc789",
"dialogues": [
{
"id": "diag_01h900000000000000000001",
"speaker": "caller",
"channel_index": 0,
"text": "Hello, I would like to check my order status for #FT10294.",
"confidence": 0.98,
"duration_ms": 3200,
"timestamp": "2026-07-24T00:55:10.000Z"
},
{
"id": "diag_01h900000000000000000002",
"speaker": "agent",
"channel_index": 1,
"text": "Certainly! I have retrieved order #FT10294 for Alex Rivera. It is scheduled for delivery today with Express Courier.",
"confidence": 0.99,
"duration_ms": 5400,
"timestamp": "2026-07-24T00:55:20.000Z"
}
],
"entities": [
{
"type": "order_id",
"value": "#FT10294",
"confidence": 0.99
},
{
"type": "customer_name",
"value": "Alex Rivera",
"confidence": 0.98
},
{
"type": "carrier",
"value": "Express Courier",
"confidence": 0.97
}
],
"total_dialogues": 2,
"completed_at": "2026-07-24T00:56:06.000Z"
}
}
Event Types
| Event | Description |
|---|---|
call.created | A new call has been initiated |
call.answered | A call was answered by an agent or user |
call.ended | A call has ended |
call.recording.ready | A call recording audio file has finished processing, uploaded, and is ready for playback/download |
call.transcription.completed | Real-time call transcription and Named Entity Recognition (NER) completed |
contact.created | A new contact was created in the workspace |
contact.updated | An existing contact was updated |
contact.deleted | A contact was deleted |
agent.state | An agent's presence state was changed (available, busy, away, offline) |
agent.created | A new workspace agent account was created |
agent.updated | An existing workspace agent account was updated |
agent.deleted | A workspace agent account was deleted |
workspace.credit.updated | Workspace credit balance was updated (e.g. call charge deduction, top-up) |
Retry Policy
If your endpoint returns a non-2xx status code or times out (30 seconds), Firetell will retry the delivery:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
After 4 failed retries, the webhook is marked as failed and no further attempts are made.
Security
Validate incoming webhooks by checking the X-Webhook-Signature header. This header contains an HMAC-SHA256 signature of the JSON request body, signed with your webhook secret (prefixed with whse-).
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Usage in an Express handler:
app.post('/webhooks/firetell', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const isValid = verifySignature(
JSON.stringify(req.body),
signature,
'whse-your-webhook-secret'
);
if (!isValid) return res.status(401).send('Invalid signature');
// Process event...
res.status(200).send('OK');
});
Best Practices
- Respond quickly — Return
200immediately and process asynchronously - Handle duplicates — Use the event ID for idempotency
- Use HTTPS — Always use HTTPS endpoints in production
- Validate signatures — Verify the
X-Webhook-Signatureheader