> ## Documentation Index
> Fetch the complete documentation index at: https://docs.buildwithchirp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks Setup

> Configure webhooks to receive real-time notifications about call events

Webhooks allow you to receive real-time HTTP notifications when call events occur in your application, such as incoming calls, call completions, recordings, and voicemails.

For general webhook concepts, see [Webhooks](/concepts/webhooks).

## Creating a Webhook

Subscribe to calling events by creating a webhook for your application.

**Via Dashboard**

1. Navigate to your application's Webhooks page
2. Click "Create Webhook"
3. Enter your webhook URL
4. Select the calling events you want to receive
5. (Optional) Add custom headers for authentication
6. Save the webhook

**Via API**

Use the `POST /v1/webhooks` endpoint with calling event types:

```bash icon="terminal" title="Create Calling Webhook" theme={null}
curl -X POST https://api.buildwithchirp.com/v1/webhooks \
  -H "Authorization: Bearer YOUR_APP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/chirp",
    "events": [
      "calls.initiated",
      "calls.answered",
      "calls.completed",
      "calls.failed",
      "calls.recording.completed",
      "calls.voicemail.received"
    ],
    "headers": {
      "X-Webhook-Secret": "your-secret-key"
    }
  }'
```

## Webhook Payload Structure

Every calling webhook includes these top-level fields:

| Field       | Type   | Description                                            |
| ----------- | ------ | ------------------------------------------------------ |
| `event`     | string | The event type (e.g., `calls.completed`)               |
| `eventId`   | string | Unique event identifier for idempotency                |
| `timestamp` | string | ISO 8601 timestamp of when the event was generated     |
| `data`      | object | Event-specific data including `call` and `app` objects |

### Call Data

The `data.call` object is present in all calling webhooks and includes:

| Field             | Type   | Description                                                       |
| ----------------- | ------ | ----------------------------------------------------------------- |
| `id`              | string | Unique call identifier (e.g., `call_2DbBs7GWhGvVNJGrDXr5RG0mBWI`) |
| `direction`       | string | `inbound` or `outbound`                                           |
| `from`            | string | Phone number or identifier that initiated the call                |
| `to`              | string | Phone number or identifier that received the call                 |
| `fromChannel`     | string | Channel of the originator: `pstn`, `whatsapp`, or `webrtc`        |
| `toChannel`       | string | Channel of the recipient: `pstn`, `whatsapp`, or `webrtc`         |
| `livekitRoomName` | string | LiveKit room name (present when channel is `webrtc`)              |
| `metadata`        | object | Custom metadata attached to the call (if any)                     |

## Call Lifecycle Events

These events track the progress of a call from initiation to completion.

### `calls.initiated`

Fired when a call is created (outbound) or an incoming call is received (inbound).

```json icon="json" title="calls.initiated payload" theme={null}
{
  "event": "calls.initiated",
  "eventId": "evt_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}
```

### `calls.ringing`

Fired when an outbound call is ringing at the destination.

```json icon="json" title="calls.ringing payload" theme={null}
{
  "event": "calls.ringing",
  "eventId": "evt_3EcCt8HXiHwWOKHsEYs6SH1nCXJ",
  "timestamp": "2024-01-15T10:30:02.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}
```

### `calls.answered`

Fired when the call is answered. Includes `answeredAt` and `answeredBy` fields.

```json icon="json" title="calls.answered payload" theme={null}
{
  "event": "calls.answered",
  "eventId": "evt_4FdDu9IYjIxXPLItFZt7TI2oDYK",
  "timestamp": "2024-01-15T10:30:05.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn",
      "answeredAt": "2024-01-15T10:30:05.000Z",
      "answeredBy": "+15559876543"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}
```

### `calls.completed`

Fired when a call ends normally. Includes `duration`, `endReason`, and `endedAt` fields.

```json icon="json" title="calls.completed payload" theme={null}
{
  "event": "calls.completed",
  "eventId": "evt_5GeEv0JZkJyYQMJuGAu8UJ3pEZL",
  "timestamp": "2024-01-15T10:32:00.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn",
      "duration": 120,
      "endReason": "hangup",
      "endedAt": "2024-01-15T10:32:00.000Z"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}
```

### `calls.failed`

Fired when a call fails. Includes an `error` object with details about the failure.

```json icon="json" title="calls.failed payload" theme={null}
{
  "event": "calls.failed",
  "eventId": "evt_6HfFw1KAlKzZRNKvHBv9VK4qFAM",
  "timestamp": "2024-01-15T10:30:03.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    },
    "error": {
      "type": "provider_error",
      "code": "call_failed",
      "message": "The call could not be connected. The destination number may be unreachable."
    }
  }
}
```

### `calls.no_answer`

Fired when no one answers the call.

```json icon="json" title="calls.no_answer payload" theme={null}
{
  "event": "calls.no_answer",
  "eventId": "evt_8JhHy3MCnMBBTPOxJDx1XM6sHCO",
  "timestamp": "2024-01-15T10:30:30.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}
```

## Participant Events

These events track participants joining and leaving a call.

### `calls.participant_joined`

Fired when a participant joins the call.

```json icon="json" title="calls.participant_joined payload" theme={null}
{
  "event": "calls.participant_joined",
  "eventId": "evt_0LjJA5OEpQDDVRQzLFz3ZO8uJEQ",
  "timestamp": "2024-01-15T10:30:05.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "participant": {
      "id": "part_abc123",
      "type": "phone",
      "displayName": "John Doe",
      "phoneNumber": "+15559876543",
      "joinedAt": "2024-01-15T10:30:05.000Z"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}
```

### `calls.participant_left`

Fired when a participant leaves the call. Includes the participant's `duration` and `leftAt` timestamp.

```json icon="json" title="calls.participant_left payload" theme={null}
{
  "event": "calls.participant_left",
  "eventId": "evt_1MkKB6PFqREEWSRAMGA4AP9vKFR",
  "timestamp": "2024-01-15T10:32:00.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "participant": {
      "id": "part_abc123",
      "type": "phone",
      "displayName": "John Doe",
      "phoneNumber": "+15559876543",
      "joinedAt": "2024-01-15T10:30:05.000Z",
      "duration": 115,
      "leftAt": "2024-01-15T10:32:00.000Z"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}
```

## Recording and Voicemail Events

### `calls.recording.completed`

Fired when a call recording has finished processing and is available for download.

```json icon="json" title="calls.recording.completed payload" theme={null}
{
  "event": "calls.recording.completed",
  "eventId": "evt_2NlLC7QGrSFFXTSBNHB5BQ0wLGS",
  "timestamp": "2024-01-15T10:33:00.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "recording": {
      "id": "call_rec_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "duration": 120,
      "url": "https://storage.buildwithchirp.com/recordings/call_rec_2DbBs7GWhGvVNJGrDXr5RG0mBWI.mp4?signed=...",
      "format": "mp4",
      "size": 1048576
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}
```

### `calls.voicemail.received`

Fired when a voicemail is left. Includes the voicemail audio URL and transcription (when available).

```json icon="json" title="calls.voicemail.received payload" theme={null}
{
  "event": "calls.voicemail.received",
  "eventId": "evt_3OmMD8RHsTGGYUSCOIC6CR1xMHT",
  "timestamp": "2024-01-15T10:31:30.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "inbound",
      "from": "+15559876543",
      "to": "+15551234567",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "voicemail": {
      "id": "call_vm_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "duration": 30,
      "url": "https://storage.buildwithchirp.com/voicemails/call_vm_2DbBs7GWhGvVNJGrDXr5RG0mBWI.mp4?signed=...",
      "transcription": "Hi, please call me back when you get a chance."
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}
```

<Info>
  The `transcription` field may be `null` if transcription is not enabled or still processing. You can retrieve the transcription later using the [voicemail API endpoints](/calling/voicemails).
</Info>

<Info>
  The `url` fields in recording and voicemail webhook payloads are time-limited signed URLs. If you need to access the file later, use the [Recordings API](/calling/recordings#get-recording-details) to generate a fresh URL.
</Info>

## Event Summary

| Event                       | Description                                                 |
| --------------------------- | ----------------------------------------------------------- |
| `calls.initiated`           | Call created (outbound) or incoming call received (inbound) |
| `calls.ringing`             | Outbound call is ringing at the destination                 |
| `calls.answered`            | Call was answered                                           |
| `calls.completed`           | Call ended normally                                         |
| `calls.failed`              | Call failed with an error                                   |
| `calls.no_answer`           | No one answered the call                                    |
| `calls.participant_joined`  | A participant joined the call                               |
| `calls.participant_left`    | A participant left the call                                 |
| `calls.recording.completed` | Recording finished processing and is available              |
| `calls.voicemail.received`  | A voicemail was left                                        |

## Handling Calling Webhooks

Here is an example of how to handle calling webhooks in your application:

```javascript icon="javascript" title="Handle Calling Webhooks" theme={null}
app.post("/webhooks/chirp", (req, res) => {
  // Acknowledge immediately
  res.status(200).send("OK");

  const { event, eventId, data } = req.body;

  switch (event) {
    case "calls.initiated":
      console.log(`Call ${data.call.id} initiated (${data.call.direction})`);
      break;

    case "calls.completed":
      console.log(`Call ${data.call.id} completed after ${data.call.duration}s`);
      break;

    case "calls.failed":
      console.error(`Call ${data.call.id} failed: ${data.error.message}`);
      break;

    case "calls.recording.completed":
      console.log(`Recording available: ${data.recording.url}`);
      break;

    case "calls.voicemail.received":
      console.log(`Voicemail from ${data.call.from}: ${data.voicemail.transcription ?? "(transcription pending)"}`);

      break;

    default:
      console.log(`Unhandled event: ${event}`);
  }
});
```

<Tip>
  Use the `eventId` field to implement idempotency. Track processed event IDs to prevent handling the same event twice if it is delivered more than once.
</Tip>
