---
sidebar_position: 12
title: Webhooks
description: Create and manage webhook endpoints to receive real-time event notifications for calls, contacts, and recordings via the Firetell REST API.
---

# Webhooks

Configure webhook endpoints to receive real-time HTTP `POST` notifications when events occur in your workspace — such as incoming calls, call state changes, call endings, and contact updates.

For details on payload format, event types, retry policy, and signature verification, see the [Webhooks Overview](/docs/webhooks/overview).

## Endpoints

| Method   | Endpoint                   | Description                |
| -------- | -------------------------- | -------------------------- |
| `GET`    | `/webhooks`                | List all webhook endpoints |
| `GET`    | `/webhooks/:id`            | Get webhook details        |
| `POST`   | `/webhooks`                | Create a webhook endpoint  |
| `POST`   | `/webhooks/:id/test-event` | Send a test event          |
| `PATCH`  | `/webhooks/:id`            | Update a webhook endpoint  |
| `DELETE` | `/webhooks/:id`            | Delete a webhook endpoint  |

## The Webhook Object

| Field          | Type     | Description                                                           |
| -------------- | -------- | --------------------------------------------------------------------- |
| `id`           | string   | Unique webhook identifier (prefixed with `wh-`)                       |
| `workspace_id` | string   | Workspace identifier                                                  |
| `title`        | string   | Display name for the webhook                                          |
| `url`          | string   | The URL that receives event notifications                             |
| `events`       | string[] | Array of event names this webhook is subscribed to                    |
| `headers`      | object[] | Custom HTTP headers sent with each delivery                           |
| `is_active`    | boolean  | Whether the webhook is active                                         |
| `secret`       | string   | Signing secret for signature verification (only returned on creation) |
| `created_at`   | string   | ISO 8601 creation timestamp                                           |
| `updated_at`   | string   | ISO 8601 last update timestamp                                        |

### Webhook Header Object

| Field   | Type   | Description                     |
| ------- | ------ | ------------------------------- |
| `key`   | string | Header name (1–35 characters)   |
| `value` | string | Header value (1–255 characters) |

### Available Event Names

For complete payload structures and JSON schemas for each event, see the [Webhook Event Catalog](/docs/webhooks/event-catalog).

| 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 |
| `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, etc) |
| `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) |

---

## List Webhooks

```
GET /webhooks
```

Retrieve a paginated list of all webhook endpoints in the workspace.

### Authentication

Requires `ApiKey` with any scope.

### Query Parameters

| Parameter    | Type   | Default      | Description                       |
| ------------ | ------ | ------------ | --------------------------------- |
| `page`       | number | `1`          | Page number (min: 1)              |
| `limit`      | number | `10`         | Items per page (min: 1, max: 100) |
| `search`     | string | —            | Search by title                   |
| `sort_field` | string | `created_at` | Sort field. Allowed: `created_at` |
| `sort_order` | string | `desc`       | Sort order: `asc` or `desc`       |

### Request

```bash
curl -X GET "https://{workspace_id}.firetell.app/api/v1/webhooks?page=1&limit=10" \
  -H "Authorization: ApiKey sk-YOUR_API_KEY"
```

### Response

```json
{
  "data": [
    {
      "id": "wh-7f3a2b1c9d4e5f6a8b0c1d2e",
      "workspace_id": "yourcompany",
      "title": "Call Events - Production",
      "url": "https://api.example.com/webhooks/firetell",
      "events": ["call.created", "call.ended"],
      "headers": [
        {
          "key": "X-Custom-Token",
          "value": "my-secret-token"
        }
      ],
      "is_active": true,
      "created_at": "2026-01-15T08:30:00.000Z",
      "updated_at": "2026-03-20T14:00:00.000Z"
    },
    {
      "id": "wh-9a8b7c6d5e4f3a2b1c0d9e8f",
      "workspace_id": "yourcompany",
      "title": "Contact Sync",
      "url": "https://crm.example.com/hooks/firetell",
      "events": ["contact.created", "contact.updated", "contact.deleted"],
      "headers": [],
      "is_active": true,
      "created_at": "2026-02-10T10:00:00.000Z",
      "updated_at": "2026-02-10T10:00:00.000Z"
    }
  ],
  "meta": {
    "total": 2,
    "page": 1,
    "limit": 10,
    "total_pages": 1
  }
}
```

:::info
The `secret` field is not included in list responses. It is only returned when creating a webhook.
:::

---

## Get Webhook

```
GET /webhooks/:id
```

Retrieve details of a specific webhook endpoint.

### Authentication

Requires `ApiKey` with `full` scope.

### Path Parameters

| Parameter | Type   | Description                                          |
| --------- | ------ | ---------------------------------------------------- |
| `id`      | string | The webhook ID (e.g., `wh-7f3a2b1c9d4e5f6a8b0c1d2e`) |

### Request

```bash
curl -X GET "https://{workspace_id}.firetell.app/api/v1/webhooks/wh-7f3a2b1c9d4e5f6a8b0c1d2e" \
  -H "Authorization: ApiKey sk-YOUR_API_KEY"
```

### Response

```json
{
  "id": "wh-7f3a2b1c9d4e5f6a8b0c1d2e",
  "workspace_id": "yourcompany",
  "title": "Call Events - Production",
  "url": "https://api.example.com/webhooks/firetell",
  "events": ["call.created", "call.ended"],
  "headers": [
    {
      "key": "X-Custom-Token",
      "value": "my-secret-token"
    }
  ],
  "is_active": true,
  "created_at": "2026-01-15T08:30:00.000Z",
  "updated_at": "2026-03-20T14:00:00.000Z"
}
```

### Error Response

```json
{
  "statusCode": 404,
  "message": "item not found",
  "error": "Not Found"
}
```

---

## Create Webhook

```
POST /webhooks
```

Create a new webhook endpoint. A signing secret is automatically generated and returned in the response — **save it securely**, as it will not be retrievable again.

### Authentication

Requires `ApiKey` with `full` scope.

:::caution
Each workspace is limited to a maximum of **20 webhook endpoints**. Exceeding this limit will return a `403 Forbidden` error.
:::

### Request Body

| Field     | Type     | Required | Description                                                                          |
| --------- | -------- | -------- | ------------------------------------------------------------------------------------ |
| `title`   | string   | ✅       | Display name. 1–100 characters.                                                      |
| `url`     | string   | ✅       | Webhook URL. Must be a valid HTTPS URL. Max 255 characters.                          |
| `events`  | string[] | ✅       | Array of event names to subscribe to. At least 1 event required.                     |
| `headers` | object[] | —        | Custom HTTP headers (max 3). Each with `key` (1–35 chars) and `value` (1–255 chars). |

### Request

```bash
curl -X POST "https://{workspace_id}.firetell.app/api/v1/webhooks" \
  -H "Authorization: ApiKey sk-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Call Events - Production",
    "url": "https://api.example.com/webhooks/firetell",
    "events": ["call.created", "call.answered", "call.ended"],
    "headers": [
      {
        "key": "X-Custom-Token",
        "value": "my-secret-token"
      }
    ]
  }'
```

### Response

```json
{
  "id": "wh-d4e5f6a7b8c9d0e1f2a3b4c5",
  "workspace_id": "yourcompany",
  "title": "Call Events - Production",
  "url": "https://api.example.com/webhooks/firetell",
  "events": ["call.created", "call.answered", "call.ended"],
  "headers": [
    {
      "key": "X-Custom-Token",
      "value": "my-secret-token"
    }
  ],
  "is_active": true,
  "secret": "whse-a1b2c3d4e5f6a7b8c9d0e1",
  "created_at": "2026-07-09T08:00:00.000Z",
  "updated_at": "2026-07-09T08:00:00.000Z"
}
```

:::caution
**Save the `secret` value immediately.** The secret (prefixed with `whse-`) is only returned once during creation. You will need it to verify webhook signatures using HMAC-SHA256.
:::

### Error Response

**Webhook limit reached:**

```json
{
  "statusCode": 403,
  "message": "We currently only support up to 20 webhooks with your plan. You've reached your limit.",
  "error": "Forbidden"
}
```

---

## Send Test Event

```
POST /webhooks/:id/test-event
```

Send a simulated test event to verify your webhook endpoint is working correctly. The test payload is sent immediately to the configured URL with a valid signature.

### Authentication

Requires `ApiKey` with `full` scope.

### Path Parameters

| Parameter | Type   | Description    |
| --------- | ------ | -------------- |
| `id`      | string | The webhook ID |

### Request Body

| Field        | Type   | Required | Description                                                                         |
| ------------ | ------ | -------- | ----------------------------------------------------------------------------------- |
| `event_name` | string | ✅       | The event type to simulate. Must be one of the events the webhook is subscribed to. |

### Request

```bash
curl -X POST "https://{workspace_id}.firetell.app/api/v1/webhooks/wh-7f3a2b1c9d4e5f6a8b0c1d2e/test-event" \
  -H "Authorization: ApiKey sk-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "event_name": "call.created"
  }'
```

### Success Response

```json
{
  "success": true,
  "status": 200,
  "status_text": "OK",
  "latency_ms": 142,
  "message": "Webhook test event with respond success"
}
```

### Failure Responses

**Endpoint returned non-success status:**

```json
{
  "success": false,
  "status": 500,
  "status_text": "Internal Server Error",
  "latency_ms": 85,
  "error": "Internal Server Error",
  "message": "Webhook endpoint did not respond success. status >= 300"
}
```

**Endpoint unreachable:**

```json
{
  "success": false,
  "status": 0,
  "latency_ms": 0,
  "error": "ECONNREFUSED",
  "message": "Webhook endpoint did not respond"
}
```

**Event not subscribed:**

```json
{
  "statusCode": 400,
  "message": "This endpoint is not currently subscribed to this event",
  "error": "Bad Request"
}
```

### Test Payload Example

When simulating a `call.created` event, the following test payload is sent to your URL:

```json
{
  "event": "call.created",
  "data": {
    "workspace_id": "yourcompany",
    "call_id": "test_call_id_1720512000000",
    "direction": "inbound",
    "caller": {
      "name": "Caller_name",
      "number": "Caller_number"
    },
    "callee": {
      "name": "Callee_name",
      "number": "Callee_number"
    },
    "created_at": "2026-07-09T08:00:00.000Z"
  }
}
```

:::tip
Use the test event endpoint during development to verify your webhook handler is correctly parsing payloads and validating signatures before going live.
:::

---

## Update Webhook

```
PATCH /webhooks/:id
```

Update an existing webhook endpoint's configuration. All fields in the request body are required — this endpoint replaces the webhook configuration entirely.

### Authentication

Requires `ApiKey` with `full` scope.

### Path Parameters

| Parameter | Type   | Description    |
| --------- | ------ | -------------- |
| `id`      | string | The webhook ID |

### Request Body

| Field     | Type     | Required | Description                                                 |
| --------- | -------- | -------- | ----------------------------------------------------------- |
| `title`   | string   | ✅       | Display name. 1–100 characters.                             |
| `url`     | string   | ✅       | Webhook URL. Must be a valid HTTPS URL. Max 255 characters. |
| `events`  | string[] | ✅       | Array of event names. At least 1 event required.            |
| `headers` | object[] | —        | Custom HTTP headers (max 3).                                |

### Request

```bash
curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/webhooks/wh-7f3a2b1c9d4e5f6a8b0c1d2e" \
  -H "Authorization: ApiKey sk-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Call Events - Updated",
    "url": "https://api.example.com/webhooks/firetell-v2",
    "events": ["call.created", "call.ended"],
    "headers": []
  }'
```

### Response

Returns the updated webhook object:

```json
{
  "id": "wh-7f3a2b1c9d4e5f6a8b0c1d2e",
  "workspace_id": "yourcompany",
  "title": "Call Events - Updated",
  "url": "https://api.example.com/webhooks/firetell-v2",
  "events": ["call.created", "call.ended"],
  "headers": [],
  "is_active": true,
  "created_at": "2026-01-15T08:30:00.000Z",
  "updated_at": "2026-07-09T09:30:00.000Z"
}
```

:::info
Updating a webhook does **not** regenerate the signing secret. The original secret remains valid.
:::

### Error Response

```json
{
  "statusCode": 404,
  "message": "item not found",
  "error": "Not Found"
}
```

---

## Delete Webhook

```
DELETE /webhooks/:id
```

Permanently delete a webhook endpoint. Event notifications will immediately stop being delivered to this URL.

### Authentication

Requires `ApiKey` with `full` scope.

:::caution
This action is irreversible. Any events that occur after deletion will not be delivered to this endpoint.
:::

### Path Parameters

| Parameter | Type   | Description    |
| --------- | ------ | -------------- |
| `id`      | string | The webhook ID |

### Request

```bash
curl -X DELETE "https://{workspace_id}.firetell.app/api/v1/webhooks/wh-7f3a2b1c9d4e5f6a8b0c1d2e" \
  -H "Authorization: ApiKey sk-YOUR_API_KEY"
```

### Response

Returns the deleted webhook object:

```json
{
  "id": "wh-7f3a2b1c9d4e5f6a8b0c1d2e",
  "workspace_id": "yourcompany",
  "title": "Call Events - Production",
  "url": "https://api.example.com/webhooks/firetell",
  "events": ["call.created", "call.ended"],
  "headers": [],
  "is_active": true,
  "created_at": "2026-01-15T08:30:00.000Z",
  "updated_at": "2026-03-20T14:00:00.000Z"
}
```

### Error Response

```json
{
  "statusCode": 404,
  "message": "item not found",
  "error": "Not Found"
}
```

---

## Webhook Delivery

### Signature Verification

Every webhook delivery includes an `X-Webhook-Signature` header containing an HMAC-SHA256 signature of the request body, signed with your webhook's `secret`.

```javascript
const crypto = require("crypto");

function verifyWebhookSignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

// In your Express handler:
app.post("/webhooks/firetell", (req, res) => {
  const signature = req.headers["x-webhook-signature"];
  const isValid = verifyWebhookSignature(
    JSON.stringify(req.body),
    signature,
    "whse-your-webhook-secret",
  );

  if (!isValid) {
    return res.status(401).send("Invalid signature");
  }

  // Process the event...
  res.status(200).send("OK");
});
```

### Delivery Headers

Each webhook delivery includes the following headers:

| Header                | Description                                                                    |
| --------------------- | ------------------------------------------------------------------------------ |
| `Content-Type`        | Always `application/json`                                                      |
| `X-Webhook-Signature` | HMAC-SHA256 signature of the request body                                      |
| `X-Powered-By`        | Firetell service identifier                                                    |
| `User-Agent`          | Firetell webhook delivery agent (e.g., `region@firetell.shared.webhook/1.0.2`) |
| Custom headers        | Any headers you configured on the webhook endpoint                             |

### Timeout & Retries

- **Timeout:** 5 seconds per delivery attempt
- **Redirect:** Not followed (`maxRedirects: 0`)
- Your endpoint must respond with a status code **below 300** to be considered successful
- See [Webhooks Overview](/docs/webhooks/overview) for the retry policy
