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

# Calls and Recordings

> Get a webhook when a call on one of your numbers ends, then download its recording, transcript and summary.

Blujay reports the phone and FaceTime Audio calls on your Blujay phone numbers through two webhooks. `call.ended` arrives once when a call is over, whether it was answered, missed or went to voicemail. `call.recording_ready` arrives once when the call's recording is ready, with its transcript and summary when Blujay has them. You download the audio itself from `GET /v1/calls/{call_id}/recording` with your API key.

> [!WARNING]
>
> **Recording consent is your responsibility.** Some US states require everyone on a call to agree before it's recorded. Make sure your calls meet the laws that apply to you and the people you call, and handle recordings and transcripts as personal data.

## Subscribe to call events

Add `call.ended` and `call.recording_ready` to a webhook subscription's `event_types`. You can subscribe to either one on its own.

```bash curl theme={null}
curl -X POST https://api.blujay.io/v1/webhook-subscriptions \
  -H "Authorization: Bearer blu_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/blujay",
    "event_types": ["call.ended", "call.recording_ready"]
  }'
```

```json Response (201) theme={null}
{
  "status": "success",
  "data": {
    "subscription_id": "whsub_abc123",
    "url": "https://example.com/webhooks/blujay",
    "event_types": ["call.ended", "call.recording_ready"],
    "status": "active",
    "phone_number_filter": { "mode": "all" },
    "webhook_version": "2026-10-03",
    "signing_secret_hint": "Y3OA==",
    "created_at": "2026-10-14T17:01:00.000Z",
    "updated_at": "2026-10-14T17:01:00.000Z",
    "signing_secret": "whsec_ZXhhbXBsZS1zZWNyZXQtZG8tbm90LXVzZS0xMjM0NTY3OA=="
  },
  "trace_id": "trc_8f2a1c0e4b7d",
  "timestamp": "2026-10-14T17:01:00.000Z"
}
```

To add calls to a subscription you already have, send `PATCH /v1/webhook-subscriptions/{subscription_id}` with the full list you want. `event_types` replaces the current list, so include your existing events too.

Call events follow the subscription's `phone_number_filter`: a subscription limited to some numbers only gets calls on those numbers. See [Select phone numbers](https://docs.blujay.io/webhooks/overview.md#select-phone-numbers). The call payloads are the same on every webhook version.

## Know when each event arrives

* **`call.ended`** is sent once, when the call is over. An incoming call nobody answered is sent as `missed` as soon as the ringing stops. An outgoing phone call can take up to about a minute after it ends, while Blujay works out whether it reached voicemail.
* **`call.recording_ready`** is sent once, when the call's recording is ready: after its transcript and summary are done, or after at most 30 minutes. A call with no recording, such as a missed call where the caller left no voicemail, sends only `call.ended`.

Both events carry the full call details, so you can handle whichever one arrives first. Deduplicate by `event_id`, as for every webhook.

## Read the call.ended payload

```json call.ended theme={null}
{
  "api_version": "v1",
  "webhook_version": "2026-10-03",
  "event_type": "call.ended",
  "event_id": "evt_abc123",
  "created_at": "2026-10-14T17:14:21.000Z",
  "trace_id": "trc_8f2a1c0e4b7d",
  "workspace_id": "workspace_abc123",
  "subscription_id": "whsub_abc123",
  "data": {
    "call_id": "call_abc123",
    "phone_number_id": "phone_abc123",
    "direction": "inbound",
    "type": "phone",
    "outcome": "answered",
    "from": "+15555550123",
    "to": "+15555550100",
    "caller_id_blocked": false,
    "contact_id": "contact_abc123",
    "team_member_id": "member_abc123",
    "started_at": "2026-10-14T17:10:00.000Z",
    "answered_at": "2026-10-14T17:10:08.000Z",
    "ended_at": "2026-10-14T17:14:20.000Z",
    "duration_seconds": 252
  }
}
```

| Field | Meaning |
| - | - |
| `call_id` | The call. Use it to download the recording and to match `call.recording_ready` to this event. |
| `phone_number_id` | Your Blujay phone number the call was on. |
| `direction` | `inbound` when someone called your number, `outbound` when your number placed the call. |
| `type` | `phone` for a phone call, `facetime_audio` for a FaceTime Audio call. |
| `outcome` | How the call ended. See the table below. |
| `from` | Who called: a phone number, or a FaceTime address. `null` when an incoming caller blocked their number. |
| `to` | The number that was called. For an incoming call, this is your Blujay number the caller dialed. |
| `caller_id_blocked` | `true` when an incoming caller blocked their number. |
| `contact_id` | The other person's contact in your workspace, or `null` when there isn't one. |
| `team_member_id` | The teammate who placed or was assigned the call, or `null`. |
| `started_at`, `answered_at`, `ended_at` | When the call started, was answered and ended. `answered_at` is `null` when nobody answered. |
| `duration_seconds` | Talk time in seconds. `0` when nobody answered. |

### Outcomes

| `outcome` | Meaning |
| - | - |
| `answered` | Someone picked up. |
| `missed` | Nobody answered, the number was busy, or the caller hung up first. |
| `voicemail` | An outgoing call reached voicemail, or an incoming call went straight to voicemail. |
| `connected` | An outgoing call connected, but Blujay couldn't tell whether a person or voicemail answered. |
| `failed` | The call couldn't be completed. |

An incoming call that rang out stays `missed` in `call.ended`, even if the caller then leaves a voicemail. The voicemail arrives later in `call.recording_ready`, with `outcome: "voicemail"`. See [Voicemails](#voicemails).

### Find the person

When `contact_id` is set, read the contact with `GET /v1/contacts/{contact_id}`:

```bash curl theme={null}
curl https://api.blujay.io/v1/contacts/contact_abc123 \
  -H "Authorization: Bearer blu_your_api_key"
```

When it is `null`, the other person isn't a contact in your workspace yet. Use `from` for an incoming call or `to` for an outgoing one. A blocked caller has no number at all: `from` is `null` and `caller_id_blocked` is `true`.

To match `team_member_id` to a name, list your workspace's active members with `GET /v1/team-members/assignable`. See [Assign a contact](https://docs.blujay.io/contacts.md#assign-a-contact).

## Get the recording

`call.recording_ready` carries every `call.ended` field, plus `recording`, `transcript` and `summary`. Only `data` is shown here; the envelope is the same as above.

```json call.recording_ready theme={null}
{
  "call_id": "call_abc123",
  "phone_number_id": "phone_abc123",
  "direction": "inbound",
  "type": "phone",
  "outcome": "answered",
  "from": "+15555550123",
  "to": "+15555550100",
  "caller_id_blocked": false,
  "contact_id": "contact_abc123",
  "team_member_id": "member_abc123",
  "started_at": "2026-10-14T17:10:00.000Z",
  "answered_at": "2026-10-14T17:10:08.000Z",
  "ended_at": "2026-10-14T17:14:20.000Z",
  "duration_seconds": 252,
  "recording": {
    "download_url": "/v1/calls/call_abc123/recording",
    "duration_seconds": 252,
    "kind": "call"
  },
  "transcript": {
    "status": "completed",
    "text": "Hi, this is Ava. I'm calling to move Friday's appointment to the afternoon.",
    "language": "en-US"
  },
  "summary": {
    "status": "completed",
    "text": "Ava asked to move Friday's appointment to the afternoon."
  }
}
```

* `recording.download_url` is a path on the Blujay API. `recording.duration_seconds` is the recording's length, or `null` when Blujay doesn't know it. `recording.kind` is `call` for a recorded conversation or `voicemail` for a voicemail.
* `transcript.status` is `completed` with the `text` and its `language`, or `unavailable` with both `null`. `language` is also `null` when the call was transcribed in multi-language mode.
* `summary.status` is `completed` with the `text`, or `unavailable` with `text: null` when there was too little conversation to summarize or the summary failed.

Save the transcript and summary when the event arrives. The API has no endpoint that returns them later.

### Download the audio

Call the `download_url` path on `https://api.blujay.io` with your usual `Authorization` header:

```bash curl theme={null}
curl https://api.blujay.io/v1/calls/call_abc123/recording \
  -H "Authorization: Bearer blu_your_api_key" \
  -o call_abc123.mp3
```

The response body is the recording as audio, with `Content-Type: audio/mpeg`. Never send your API key to a URL on any other host.

### Voicemails

A voicemail arrives as `call.recording_ready` with `recording.kind: "voicemail"`. That covers a voicemail a caller left you and an outgoing call that reached the other person's voicemail. Download it the same way as any other recording.

When someone calls, nobody answers and they leave a voicemail, you get two events for the same `call_id`:

1. `call.ended` as soon as the ringing stops, with `outcome: "missed"`, `answered_at: null` and `duration_seconds: 0`.
2. `call.recording_ready` once the voicemail is ready, with `outcome: "voicemail"` and `recording.kind: "voicemail"`.

`call.ended` doesn't change afterwards, so `call.recording_ready` is the only event that tells you a voicemail was left.

## Errors

These come from `GET /v1/calls/{call_id}/recording`.

| Error | Status | Cause |
| - | - | - |
| [`NOT_FOUND`](https://docs.blujay.io/errors/not-found) | 404 | No such call in your workspace, the call has no recording, or the recording is no longer available. |
| [`RECORDING_UNAVAILABLE`](https://docs.blujay.io/errors/recording-unavailable) | 503 | The recording couldn't be downloaded right now. Retry shortly, after `Retry-After` when it is present. |

## Limitations

* The API reports calls; it doesn't make or control them. There are no endpoints to place, answer or end a call, or to list or read calls. The recording download is the only call endpoint.
* There are no events while a call is in progress, such as ringing or answered. `call.ended` is the first event for a call.
* Transcripts and summaries arrive only in `call.recording_ready`. Store them when you receive them.
* A call doesn't count as a response from the contact under the [sending rules](https://docs.blujay.io/messaging/sending-rules.md#what-counts-as-a-response).
* To see whether a number is ready for calls, read `capabilities.voice` on `GET /v1/phone-numbers`. Free Plan numbers leave it out. See [Phone numbers](https://docs.blujay.io/phone-numbers.md#understand-the-fields).

## Next steps

> [card] [Webhook events](https://docs.blujay.io/webhooks/events.md#calls)
>
> The call event payloads next to every other event.

> [card] [Webhooks overview](https://docs.blujay.io/webhooks/overview.md)
>
> Set up an endpoint, verify signatures and pick numbers.

> [card] [Contacts](https://docs.blujay.io/contacts.md)
>
> Look up and update the person behind a call.

> [card] [Phone numbers](https://docs.blujay.io/phone-numbers.md)
>
> Your numbers and whether each is ready for calls.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.