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

# Webhooks

> How Paylead pushes real-time events to your back-office and how to make your endpoint reliable.

[Webhooks](/glossary#webhooks) are outbound HTTP notifications Paylead sends when something happens in the platform: a [Reward](/glossary#reward) is created, validated, or paid out. They let the [Program Manager](/glossary#program-manager) react in real time without polling the API.

Two principles to remember:

1. The Webhook is a **trigger**, not a source of truth. Always re-fetch the underlying object via the API before acting on it.
2. **Ordering is not guaranteed.** Events for the same object may arrive out of order. Trust the status returned by the API, not the sequence of events.

## Payload structure

Every Webhook is a `POST` request with a JSON body containing four fields.

```json theme={null}
{
  "event_type": "REWARD_CREATED",
  "resource_id": "550e8400-e29b-41d4-a716-446655440000",
  "consumer_id": "ext-user-123",
  "user_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}
```

| Field         | Type                  | Description                                                                                                 |
| ------------- | --------------------- | ----------------------------------------------------------------------------------------------------------- |
| `event_type`  | string                | The event identifier.                                                                                       |
| `resource_id` | string (UUID)         | The ID of the primary object the event relates to.                                                          |
| `consumer_id` | string or null        | The external [Consumer](/glossary#consumer) ID. Null for payout, [Offer](/glossary#offer), and file events. |
| `user_id`     | string (UUID) or null | The internal Paylead user ID. Null for payout, Offer, and file events.                                      |

## Event catalog

Paylead emits two parallel event families for Rewards: **consumer-facing events** and **technical events**. They are independent, each tracking a different perspective on the same lifecycle.

<Tip>
  Use consumer-facing events to drive Consumer animation: push notifications, in-app Reward status updates, and any display visible to the Consumer. Technical events are for back-office processing (accounting, payout pipelines).
</Tip>

<AccordionGroup>
  <Accordion title="Consumer-facing Reward events">
    Track what the [Consumer](/glossary#consumer) experiences in the bank app. These events fire when the Consumer's view of a Reward changes, independently of Paylead's internal processing.

    | Event                        | Description                                                                       | `resource_id` |
    | ---------------------------- | --------------------------------------------------------------------------------- | ------------- |
    | `consumer_reward_validation` | The Reward has entered validation; show a pending status to the Consumer.         | Reward ID     |
    | `consumer_reward_pooled`     | The Reward has landed in the Consumer's pool; the Consumer can see it in the app. | Reward ID     |
    | `consumer_reward_paid`       | The Reward has been paid to the Consumer's account.                               | Reward ID     |
    | `consumer_reward_cancelled`  | The Reward was cancelled (refund, dispute, or expiry).                            | Reward ID     |
  </Accordion>

  <Accordion title="Reward technical events">
    Sent on every change to the internal processing status of a [Cashback](/glossary#cashback) or [Gift](/glossary#gift). Use these for back-office operations.

    | Event              | Description                                                   | `resource_id` |
    | ------------------ | ------------------------------------------------------------- | ------------- |
    | `reward_created`   | A new Reward is created in `PENDING_VALIDATION`.              | Reward ID     |
    | `reward_validated` | The Reward is `VALIDATED`; Paylead now guarantees the payout. | Reward ID     |
    | `reward_paid_in`   | Paylead has received the funds from the merchant.             | Reward ID     |
    | `reward_paid_out`  | The Reward has been paid out to the Program.                  | Reward ID     |
  </Accordion>

  <Accordion title="Offer events">
    Sent when an [Offer](/glossary#offer) changes or approaches its limits. `consumer_id` and `user_id` are null for these events.

    | Event                          | Description                                                             | `resource_id` |
    | ------------------------------ | ----------------------------------------------------------------------- | ------------- |
    | `new_offer_available`          | A new Offer has been published and is available to Consumers.           | None          |
    | `offer_ending_soon_date`       | The Offer expires in less than 3 days.                                  | Offer ID      |
    | `offer_ending_soon_budget`     | The Offer budget has exceeded 90% consumption.                          | Offer ID      |
    | `offer_cashback_rate_increase` | The [Cashback](/glossary#cashback) rate has been increased (new phase). | Offer ID      |
  </Accordion>

  <Accordion title="Payout events">
    Sent when Paylead executes a payout batch for the Program. `consumer_id` and `user_id` are null for these events.

    | Event            | Description                                                                                  | `resource_id` |
    | ---------------- | -------------------------------------------------------------------------------------------- | ------------- |
    | `payout_success` | The payout batch completed successfully. Funds have been transferred to the Program account. | Payout ID     |
    | `payout_error`   | The payout batch failed. No transfer was made.                                               | Payout ID     |
  </Accordion>

  <Accordion title="Other events">
    | Event                        | Description                                                                                              | `resource_id` |
    | ---------------------------- | -------------------------------------------------------------------------------------------------------- | ------------- |
    | `ventilation_file_available` | A [Ventilation](/glossary#ventilation) file is ready for download. `consumer_id` and `user_id` are null. | File ID       |
  </Accordion>

  <Accordion title="Deprecated events">
    Do not subscribe to these events for new integrations.

    * **Affiliation events** (`affiliation_*`): Paylead no longer offers Affiliation.
    * **`scrapping_error`**: the bank synchronization failure event is deprecated.
  </Accordion>
</AccordionGroup>

## Setting up your endpoint

Each event type is configured independently in [Shift](/glossary#shift): you assign one HTTPS endpoint per event. The same endpoint URL can receive multiple event types.

<Tip>
  Acknowledge with `200` before doing any heavy work. Push the event into your own queue and process asynchronously. Slow endpoints get retried and pile up in Paylead's queue.
</Tip>

## Securing your endpoint

Paylead offers three mechanisms to authenticate incoming Webhooks. They are all optional and can be combined on the same Program. Configure them per endpoint in [Shift](/glossary#shift).

### HMAC SHA-256

Paylead signs the raw request body with a shared secret (minimum 32 characters) using HMAC-SHA256. The signature travels in a dedicated header.

* **Signature header:** `X-paylead-signature-256`, a hex digest (not base64).
* **Timestamp header (optional):** `X-paylead-timestamp`, included in the signed string to protect against replay attacks.
* **Verification:** recompute the HMAC with the same secret over the raw body and compare it against the header.

<Warning>
  Sign and verify against the **raw** request body, not the parsed JSON. Re-serializing the body changes the bytes and breaks the signature.
</Warning>

### Basic Auth

Paylead adds a standard `Authorization: Basic <base64(username:password)>` header to every request. Use this when your endpoint already handles HTTP Basic authentication.

### Custom header

Paylead injects an arbitrary header with a static name and value defined at configuration time. Use this to pass a proprietary token your system expects, for example `X-Api-Key: <value>`.

<Note>
  On request, Paylead can also set up a mutual TLS (**mTLS**) flow for the Webhook calls it makes to your endpoint.
</Note>

## Reliability

A robust Webhook integration handles delivery failures and ordering surprises.

### Retry policy

Paylead retries any delivery that returns a non-`2xx` response or times out. The current policy is **5 attempts maximum** with an **exponential backoff of `2^n` minutes** between attempts, where `n` is the attempt number.

| Attempt     | Delay since previous | Time since first attempt |
| ----------- | -------------------- | ------------------------ |
| 1 (initial) | n/a                  | T+0                      |
| 2           | 2 min                | T+2 min                  |
| 3           | 4 min                | T+6 min                  |
| 4           | 8 min                | T+14 min                 |
| 5           | 16 min               | T+30 min                 |
| 6           | 32 min               | T+62 min                 |

After the final attempt fails, the event is marked as failed. Failed events surface in [Shift](/glossary#shift) for manual replay.

<Note>
  This retry schedule is the current default and may evolve. Build your handler so it tolerates a stricter or looser cadence: what matters is that you ack quickly and re-fetch the object via the API.
</Note>

### Ordering

<Warning>
  **Ordering is not guaranteed.** Paylead does not promise that `reward_validated` arrives after `reward_created` at your endpoint.

  Building business logic that assumes "reward\_created always comes first" will eventually break.
</Warning>

### Re-fetch before acting

Webhooks reflect state at the moment of dispatch. Between dispatch and your handler running, the underlying object may have moved on. Always issue an API call to read the current state before triggering money movement or notifications.

<Tip>
  **Test in sandbox first.** The sandbox environment replays the full Webhook flow against your endpoint with synthetic transactions. Use it to verify signature validation and your retry behaviour before exposing a production endpoint. Sandbox is also the only safe place to deliberately return `500` responses and watch how Paylead retries.
</Tip>

## Common pitfalls

| Symptom                             | Likely cause                                    | Fix                                           |
| ----------------------------------- | ----------------------------------------------- | --------------------------------------------- |
| Stale Reward status acted on        | You trusted the Webhook payload                 | Re-fetch via the API before acting.           |
| Webhook retries flooding your queue | Endpoint returns `200` too late                 | Ack first, process async.                     |
| Wrong signatures                    | Reading the parsed body instead of the raw body | Verify against `req.rawBody`, not `req.body`. |

## What's next

<CardGroup cols={2}>
  <Card title="Versioning" icon="git-branch" href="/program/user-journey/versioning">
    The mandatory `X-Api-Version` header and the version policy.
  </Card>

  <Card title="Rate limits" icon="gauge" href="/program/user-journey/rate-limits">
    Per-token request limits and backoff strategy.
  </Card>
</CardGroup>
