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

# Webhooks

> Using our REST API, the /webhooks endpoints lets a developer manage a webhook and events.

<Note>
  Add a credit card to your account to unlock access to webhooks.
</Note>

[Webhooks](https://webhooks.fyi/) are a helpful way to receive notifications about events that are occuring in Daily. In order to use webhooks, you'll need to create a web server with an endpoint that Daily can `POST` requests to as they occur. This allows you to track events in a more asynchronous and real-time fashion, instead of polling with API requests.

## Available webhook endpoints

* [Create a webhook](/reference/rest-api/webhooks/create-webhook)
* [Delete a webhook](/reference/rest-api/webhooks/delete-webhook)
* [Get webhooks](/reference/rest-api/webhooks/get-webhook)
* [Get webhook info](/reference/rest-api/webhooks/get-webhook)
* [Update a webhook](/reference/rest-api/webhooks/update-webhook)

## Configuring Webhooks

In order to configure a webhook, you'll need to create a HTTP server. You can use an online service like [webhook.site](https://www.webhook.site) for testing, or test locally using a a service like [ngrok](https://www.ngrok.com).

When an asynchronous event occurs, we'll send a `POST` request to an endpoint you provide containing a request body that details the event. Once you have a server set up with an accompanying endpoint, you can send that to the `POST /webhooks` endpoint to create a webhook. Webhook events are sent for all rooms on your domain.

```bash theme={null}
curl --location --request POST 'https://daily.co/api/v1/webhooks' \
--header 'Authorization: Bearer $TOKEN' \
--header 'Content-Type: text/plain' \
--data-raw '{
    "url": "$WEBHOOK_URL",
    "eventTypes": ["recording.started", "recording.ready-to-download"]
}'
```

Before creating your webhook, we perform a verification check to ensure your endpoint is active and capable of handling webhook events.

We'll send a `POST` request to your webhook endpoint with the following payload:

```json theme={null}
{
  "test": "test"
}
```

This verification request is signed, exactly like a real event delivery. It carries the `X-Webhook-Signature` and `X-Webhook-Timestamp` headers described in the [`hmac`](#hmac) section below. Daily signs it with the `hmac` you passed in the create request, or with the one Daily generates for you if you did not pass one.

Your endpoint needs to return a `200` status code within 8 seconds. Avoid any slow work before replying (e.g., network calls or database writes). There are no retries on this check, so a single slow or non-`200` response stops the webhook from being created.

If your endpoint returns a non-`200` status code during this check (including because it rejects a request whose signature it cannot verify), webhook creation will fail with a `400` error and the message `non-200 status code returned from webhook endpoint, recvd <status>` (the status may be `undefined`).

* If you did not pass your own `hmac`, the secret Daily signs with is only returned in the create response, so your endpoint has no way to verify this first request. Pass your own `hmac` in the create request if you want to verify the verification request too.
* If your endpoint replies with a `401` or `400` when a signature check fails, that non-`200` is what fails the creation call.

If your endpoint returns a 200 status code, we will proceed to create your webhook. The response to the initial creation request will include details of the newly created webhook, such as its ID and event subscriptions.

```json theme={null}
{
  "uuid": "0b4e4c7c-5eaf-46fe-990b-a3752f5684f5",
  "url": "$WEBHOOK_URL",
  "hmac": "NQrSA5z0FkJ44QPrFerW7uCc5kdNLv3l2FDEKDanL1U=",
  "eventTypes": ["recording.started", "recording.ready-to-download"],
  "state": "ACTIVE",
  "failedCount": 0,
  "domainId": "$DOMAIN_ID",
  "createdAt": "2023-08-15T18:28:30.317Z",
  "updatedAt": "2023-08-15T18:28:30.317Z"
}
```

<Warning>
  When creating a webhook via the `POST /webhooks` endpoint, Daily will send a signed test request to the webhook server.
  If we do not receive a `200` status code within 8 seconds, we will consider the endpoint faulty and return a `400` error with the message `non-200 status code returned from webhook endpoint, recvd <status>` (the status may be `undefined`).
  Keep the handler fast: check the signature, return the `200`, and do any slow work (network calls, database writes) after you reply. Note that you cannot check the signature on this first test request unless you passed your own `hmac`, because Daily only returns a generated one in the create response.
</Warning>

<Warning>
  The webhooks service will return an `hmac` secret that you can use to verify the signature of a webhook.
  You may also pass in an `hmac` during a `create` or `update` request, if you'd like to specify your own secret.
  This secret must be BASE-64 encoded.
</Warning>

At this point, you can use the `uuid` field and the `GET /webhooks/:uuid` endpoint to receive information about your webhook.

```bash theme={null}
curl --location --request GET 'https://daily.co/api/v1/webhooks/0b4e4c7c-5eaf-46fe-990b-a3752f5684f5' \
--header 'Authorization: Bearer $TOKEN'
```

```json theme={null}
{
  "uuid": "0b4e4c7c-5eaf-46fe-990b-a3752f5684f5",
  "url": "$WEBHOOK_URL",
  "hmac": "NQrSA5z0FkJ44QPrFerW7uCc5kdNLv3l2FDEKDanL1U=",
  "basicAuth": null,
  "eventTypes": ["recording.started", "recording.ready-to-download"],
  "state": "ACTIVE",
  "failedCount": 0,
  "lastMomentPushed": "2023-08-15T18:29:52.000Z",
  "domainId": "$DOMAIN_ID",
  "createdAt": "2023-08-15T18:28:30.000Z",
  "updatedAt": "2023-08-15T18:29:52.000Z"
}
```

## Webhook Deliveries

We attempt to deliver events to your webhook endpoints as quickly as we can after events occur. We deliver events roughly, but not strictly, in order. Additionally, in some situations you may receive duplicate event deliveries on your endpoints. You may use the `id` attribute in the body of events as an idempotency key to detect and ignore duplicate deliveries. In case of a duplicate event delivery, your system should return a `200` status code.

In rare cases, a duplicate delivery may carry a different `id` than the original event. For [`participant.joined`](/reference/rest-api/webhooks/events/participant-joined) and [`participant.left`](/reference/rest-api/webhooks/events/participant-left) events, we recommend deduplicating on the event `type` plus the payload's `session_id` instead. A `session_id` is unique to a single join (a participant who leaves and rejoins gets a new one), so a second `participant.joined` or `participant.left` event for the same `session_id` is always a duplicate and can be safely ignored.

## Webhook Structure

### `state`

Webhooks have a `state` field that may either be `"FAILED"` or `"ACTIVE"`.

Your webhook may enter the `FAILED` state if we fail to send an event to your webhook server 3 times. Successful attempts will reset this counter. We require your webhook server to send a `200` status code relatively quickly, so be sure to respond to the request as soon as you receive it. See `retryType` for an alternative error handling behavior.

If your webhook has entered a failed state, we will no longer send events to that webhook. You can re-activate a webhook by sending a `POST` request to `/webhooks/:uuid`, where `uuid` is your webhook uuid. This will once again send a signed test message to the endpoint provided, and if a `200` is returned, we will re-activate the webhook. The same rules apply as when you first created the webhook: the test message carries the `X-Webhook-Signature` and `X-Webhook-Timestamp` headers, you have 8 seconds, and there are no retries.

You may also update the other webhook fields such as `eventTypes` if needed with this endpoint.

### `hmac`

<Warning>
  The `hmac` field contains a secret that is shared between Daily and you.
  Ensure that you do not share this secret publicly, otherwise you will not be
  able to verify that an event came from Daily.
</Warning>

Webhooks provide an `hmac`, which is a BASE-64 encoded HMAC-sha256 secret that allows you to verify that the event in question actually came from Daily. You may also provide your own secret when creating a webhook, as long as it is BASE-64 encoded. When `POST`ing to your webhook server, Daily will provide two headers: `X-Webhook-Signature` and `X-Webhook-Timestamp`.

In order to verify the signature yourself, you'll need to compute the signature in a manner provided in the snippet below:

```javascript theme={null}
// You'll need to save the hmac value the API returned when you created your webhook and insert it here
let hmacSecret = 'NQrSA5z0FkJ44QPrFerW7uCc5kdNLv3l2FDEKDanL1U=';
let signature = headers['X-Webhook-Timestamp'] + '.' + JSON.stringify(event);
const base64DecodedSecret = Buffer.from(hmacSecret, 'base64');
const hmac = crypto.createHmac('sha256', base64DecodedSecret);
let computed_signature = hmac.update(signature).digest('base64');
expect(computed_signature).toStrictEqual(headers['X-Webhook-Signature']);
```

`event` is the response body from the event that was `POST`ed to your webhook server. From there, you can sign the content with the `HMAC-sha256` string, and ensure that your signature matches the one in the `X-Webhook-Signature` header. As only Daily and you hold the `hmac`, this comparison ensures that the request came from Daily.

These headers are also sent on the `{"test": "test"}` verification request that Daily makes when you create, update, or re-activate a webhook. If your endpoint rejects that request because the signature does not check out, the call fails with a `400` error and the message `non-200 status code returned from webhook endpoint, recvd <status>` (the status may be `undefined`). When Daily generates the `hmac` for you, your endpoint has no way to verify that first request, so pass your own `hmac` in the create request if you want to check it.

### `failedCount`

This is incremented every time Daily fails to send an event to the given webhook endpoint. This can happen if your server is not responding quickly enough or if it is returning a non-`200` status code. When this happens 3 times, the webhook will enter the `FAILED` state. If we have a successful response at any time before this we will reset your `failedCount` to 0. Thus, intermittent failures should not cause the circuit breaker to flip.

### `basicAuth`

You may provide a `basicAuth` field when creating a webhook if you'd like Daily to send an `Authorization` header with a `Basic {secret}` value. This can be checked by your endpoint and used as an additional shared secret to ensure that you are only processing verified events from Daily.

### `retryType`

There are currently two retry type configurations available. `circuit-breaker` is the default. You can pass this field when creating a webhook, or updating a webhook.

#### `circuit-breaker`

This is the default retry type. Every message is treated equally, and is tried at least once. Each failure to your webhook server is counted, and if it ever reaches 3 failures or greater, a circuit breaker is flipped. At this point, Daily will stop attempting to send webhooks to the server. You can close the circuit breaker by sending a `POST` request to `/webhooks/:uuid`, which will attempt to send a request to your server again. If your server resolves correctly, Daily will begin sending events.

#### `exponential`

This retry is message based, instead of the global count that `circuit-breaker` uses. While `failedCount` is still incremented, Daily will never circuit break under this retry type. Each message will be retried at most 5 times, with an exponential backoff up to 15 minutes. If a message fails being sent 5 times, it will be deleted and no longer retried.

## Webhook Events

We provide several webhook events that you can subscribe to. See the [webhook events index](/reference/rest-api/webhooks/events) for more details.

* [`meeting.started`](/reference/rest-api/webhooks/events/meeting-started)
* [`meeting.ended`](/reference/rest-api/webhooks/events/meeting-ended)
* [`participant.joined`](/reference/rest-api/webhooks/events/participant-joined)
* [`participant.left`](/reference/rest-api/webhooks/events/participant-left)
* [`waiting-participant.joined`](/reference/rest-api/webhooks/events/waiting-participant-joined)
* [`waiting-participant.left`](/reference/rest-api/webhooks/events/waiting-participant-left)
* [`recording.started`](/reference/rest-api/webhooks/events/recording-started)
* [`recording.ready-to-download`](/reference/rest-api/webhooks/events/recording-ready-to-download)
* [`recording.error`](/reference/rest-api/webhooks/events/recording-error)
* [`transcript.started`](/reference/rest-api/webhooks/events/transcript-started)
* [`transcript.ready-to-download`](/reference/rest-api/webhooks/events/transcript-ready-to-download)
* [`transcript.error`](/reference/rest-api/webhooks/events/transcript-error)
* [`streaming.started`](/reference/rest-api/webhooks/events/streaming-started)
* [`streaming.updated`](/reference/rest-api/webhooks/events/streaming-updated)
* [`streaming.ended`](/reference/rest-api/webhooks/events/streaming-ended)
* [`streaming.error`](/reference/rest-api/webhooks/events/streaming-error)
* [`batch-processor.job-finished`](/reference/rest-api/webhooks/events/batch-processor-job-finished)
* [`batch-processor.error`](/reference/rest-api/webhooks/events/batch-processor-error)
* [`dialout.connected`](/reference/rest-api/webhooks/events/dialout-connected)
* [`dialout.answered`](/reference/rest-api/webhooks/events/dialout-answered)
* [`dialout.stopped`](/reference/rest-api/webhooks/events/dialout-stopped)
* [`dialout.warning`](/reference/rest-api/webhooks/events/dialout-warning)
* [`dialout.error`](/reference/rest-api/webhooks/events/dialout-error)
* [`dialin.ready`](/reference/rest-api/webhooks/events/dialin-ready)
* [`dialin.connected`](/reference/rest-api/webhooks/events/dialin-connected)
* [`dialin.stopped`](/reference/rest-api/webhooks/events/dialin-stopped)
* [`dialin.warning`](/reference/rest-api/webhooks/events/dialin-warning)
* [`dialin.error`](/reference/rest-api/webhooks/events/dialin-error)
* [`calltransfer.triggered`](/reference/rest-api/webhooks/events/calltransfer-triggered)
* [`calltransfer.initiated`](/reference/rest-api/webhooks/events/calltransfer-initiated)
* [`calltransfer.answered`](/reference/rest-api/webhooks/events/calltransfer-answered)
* [`calltransfer.completed`](/reference/rest-api/webhooks/events/calltransfer-completed)


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