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

# Send Yorlet events to your webhook endpoints

> Learn how to use webhooks to receive notifications about events in your Yorlet account.

## What are webhooks?

Webhooks are a way for Yorlet to send real-time notifications to your server about events that happen in your Yorlet account. When an event occurs, Yorlet sends an HTTP POST request to the webhook’s configured URL with a payload of the event data.

## Use cases

Webhooks are useful for a variety of use cases, such as:

* Sending notifications to your server when events are created in your account.
* Updating your database when events occur in your account.
* Triggering actions in thrid party services like Zapier, when events occur in your account.

## Events overview

Yorlet generates events for various actions that occur in your account. You can subscribe to specific events to receive notifications about them. For example, you can subscribe to the `customer.created` event to receive a notification when a new customer is created in your account.

See the [full list of event types](/api/events) for everything you can subscribe to.

## Webhook payloads

Each delivery is an HTTP `POST` request with a JSON body describing the event:

```json theme={"theme":"dracula"}
{
  "id": "evt_1a2b3c4d",
  "object": "event",
  "created": 1718712000,
  "type": "customer.created",
  "data": {
    "object": { "id": "cus_123", "object": "customer" },
    "previous_attributes": null
  },
  "request": {
    "id": "req_abc",
    "idempotency_key": null,
    "from_dashboard": false,
    "customer_portal": false
  }
}
```

| Field                      | Description                                                                                               |
| -------------------------- | --------------------------------------------------------------------------------------------------------- |
| `id`                       | Unique identifier for the event.                                                                          |
| `object`                   | Always `event`.                                                                                           |
| `created`                  | Time the event was created, as a Unix timestamp (seconds).                                                |
| `type`                     | The event type, e.g. `customer.created`. See the [full list](/api/events).                                |
| `data.object`              | The API resource the event relates to, at the time the event occurred.                                    |
| `data.previous_attributes` | For `*.updated` events, the keys that changed and their previous values. `null` otherwise.                |
| `request`                  | Details of the API request that triggered the event, including the `idempotency_key` if one was provided. |
| `account`                  | Only present on endpoints configured for connected accounts; the ID of the account the event belongs to.  |

Your endpoint should respond with a `2xx` status code as quickly as possible to acknowledge receipt. Any other status code (or a network error) is treated as a failed delivery and is retried — see [Retries](#retries).

## Securing your webhooks

Because your webhook URL is publicly reachable, you should verify that each request genuinely came from Yorlet before acting on it. Every endpoint has a **signing secret** that Yorlet uses to sign deliveries, letting you confirm both the authenticity and the freshness of each request.

### The signing secret

The signing secret is generated automatically when you create an endpoint and begins with `whsec_`. It is sensitive and is only returned when you create, retrieve, or roll an endpoint — it is never included in list responses, so store it securely when you first receive it.

```json theme={"theme":"dracula"}
{
  "object": "webhook_endpoint",
  "id": "we_1a2b3c4d",
  "url": "https://example.com/yorlet/webhooks",
  "signing_secret": "whsec_..."
}
```

### The `Yorlet-Signature` header

When an endpoint has a signing secret, Yorlet includes a `Yorlet-Signature` header with each delivery:

```
Yorlet-Signature: t=1718712000,v1=5257a869e7ec...
```

The header contains two comma-separated values:

| Value | Description                                                                 |
| ----- | --------------------------------------------------------------------------- |
| `t`   | The timestamp the signature was generated, in seconds since the Unix epoch. |
| `v1`  | The signature, an HMAC-SHA256 (hex encoded) of the signed payload.          |

The signed payload is the timestamp and the raw request body joined with a `.`:

```
signed_payload = {t}.{raw_request_body}
signature      = HMAC_SHA256(signing_secret, signed_payload)
```

### Verifying signatures

<Steps>
  <Step title="Extract the timestamp and signature">
    Parse the `Yorlet-Signature` header to read the `t` (timestamp) and `v1` (signature) values.
  </Step>

  <Step title="Recompute the signature">
    Concatenate the timestamp, a `.`, and the **raw** request body (the exact bytes received — do not parse and re-serialize the JSON first, as that can change the payload). Compute an HMAC-SHA256 of this string using your endpoint's signing secret as the key, and hex encode it.
  </Step>

  <Step title="Compare the signatures">
    Compare your computed signature with the `v1` value using a constant-time comparison. If they match, the request is authentic.
  </Step>

  <Step title="Guard against replays">
    Reject requests where the timestamp is outside an acceptable tolerance (for example, more than five minutes from the current time) to protect against replay attacks.
  </Step>
</Steps>

<Warning>
  You must read the **raw** request body to verify the signature. Many frameworks parse the body into an object before your handler runs; re-serializing that object can produce different bytes and cause verification to fail. Configure your framework to expose the raw body for your webhook route.
</Warning>

The following example verifies a signature in Node.js:

```javascript theme={"theme":"dracula"}
import crypto from 'node:crypto';

const TOLERANCE_SECONDS = 60 * 5;

function verifyYorletSignature(rawBody, signatureHeader, signingSecret) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((part) => part.split('='))
  );
  const timestamp = Number(parts.t);
  const signature = parts.v1;

  // Reject stale requests to mitigate replay attacks.
  if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > TOLERANCE_SECONDS) {
    return false;
  }

  const expected = crypto
    .createHmac('sha256', signingSecret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  // Constant-time comparison.
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
```

### Rolling your signing secret

If a signing secret is ever exposed, roll it to generate a new one. Rolling immediately invalidates the previous secret, so update your endpoint with the new value as soon as you roll it.

```shell theme={"theme":"dracula"}
curl https://api.yorlet.com/v1/webhook_endpoints/{id}/roll_secret \
  -X POST \
  -H "Authorization: Bearer {access_token}"
```

The response includes the endpoint with its new `signing_secret`.

## Retries

If your endpoint does not return a `2xx` status code, Yorlet retries the delivery with an exponential backoff over several attempts. Make sure your handler is idempotent: a single event may be delivered more than once, so use the event `id` to detect and ignore duplicates you have already processed.
