---
sidebar_position: 1
title: Overview
description: Firetell Webhooks — receive real-time event notifications for calls, recordings, and status changes.
---

# Webhooks Overview

Webhooks allow your application to receive real-time HTTP notifications when events occur in your Firetell workspace.

## How Webhooks Work

1. You register a webhook URL via the [Webhooks API](/docs/rest-api/workspace-api/webhooks) or the Firetell Console
2. When an event occurs, Firetell sends an HTTP `POST` request to your URL
3. Your server processes the event and responds with a `200` status code

## Webhook Payload Format

All webhook payloads follow a standard envelope structure:

```json
{
  "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`
```json
{
  "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`
```json
{
  "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`
```json
{
  "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-`).

```javascript
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 `200` immediately 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-Signature` header
