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

# Overview

> Real-time notifications for invoice, payment, and subscription lifecycle events

# Webhook Events

Settlx sends HTTP POST requests to your endpoint when invoice, payment, and subscription lifecycle events occur. Webhooks let you react to events in real time — confirming orders, provisioning access, updating your database, or triggering downstream workflows — without polling the API.

## Setting up webhooks

Configure your webhook URL in **Settings → Developer** on the dashboard. This single URL receives all event types — both invoice and subscription events.

Your webhook secret is also set from **Settings → Developer**. You will need it to verify incoming requests.

## Invoice event types

| Event                                                    | When it fires                                                                                                              |
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| [`invoice.confirmed`](/webhook-events/invoice-confirmed) | Required confirmations reached, invoice ready for settlement. `data.payment_quality` distinguishes `full` from `overpaid`. |
| [`invoice.underpaid`](/webhook-events/invoice-underpaid) | Customer sent less than the required amount — invoice stays open for top-up                                                |
| [`invoice.settled`](/webhook-events/invoice-settled)     | Funds transferred to your settlement wallet — fulfil here                                                                  |
| [`invoice.expired`](/webhook-events/invoice-expired)     | Invoice expired without full payment. `data.expiry_reason` is `no_payment` or `underpaid_unresolved`.                      |
| [`invoice.failed`](/webhook-events/invoice-failed)       | Non-recoverable failure (currently fires for wrong-token resolutions). `data.failure_reason` carries the specific cause.   |

## Subscription event types

| Event                                                          | When it fires                                               |
| -------------------------------------------------------------- | ----------------------------------------------------------- |
| [`subscriber.enrolled`](/webhook-events/subscriber-enrolled)   | Customer enrolled — first invoice created, awaiting payment |
| [`subscriber.activated`](/webhook-events/subscriber-activated) | Payment confirmed — subscription is now active              |
| [`subscriber.past_due`](/webhook-events/subscriber-past-due)   | New billing cycle started — renewal invoice created         |
| [`subscriber.expired`](/webhook-events/subscriber-expired)     | Grace period elapsed without payment                        |
| [`subscriber.cancelled`](/webhook-events/subscriber-cancelled) | Subscription cancelled by merchant                          |
| [`subscriber.paused`](/webhook-events/subscriber-paused)       | Subscription paused by merchant                             |
| [`subscriber.resumed`](/webhook-events/subscriber-resumed)     | Paused subscription resumed                                 |

## Payload format

### Invoice events

Invoice events use a nested `data` envelope:

```json theme={null}
{
  "event": "invoice.settled",
  "eventId": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890_invoice.settled_1744455900000",
  "timestamp": "2024-01-15T10:04:15.000Z",
  "data": {
    "invoice": { ... },
    "payment": { ... },
    "settlement": { ... }
  }
}
```

| Field       | Type   | Description                                     |
| ----------- | ------ | ----------------------------------------------- |
| `event`     | string | Event type                                      |
| `eventId`   | string | Unique event ID — use this for idempotency      |
| `timestamp` | string | ISO 8601 timestamp when the event was generated |
| `data`      | object | Event-specific payload                          |

### Subscription events

Subscription events use a flat payload — fields are at the top level:

```json theme={null}
{
  "event": "subscriber.activated",
  "subscriberId": "9f1e2d3c-4b5a-6789-abcd-ef0123456789",
  "merchantId": "f9e8d7c6-b5a4-3210-9876-543210fedcba",
  "planId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "email": "customer@example.com",
  "status": "active",
  "currentPeriodEnd": "2026-05-19T00:00:00.000Z",
  "timestamp": "2026-04-19T10:45:00.000Z"
}
```

Both event types are delivered to the same webhook URL and signed with the same secret. Use the `event` field to distinguish them — invoice events start with `invoice.`, subscription events start with `subscriber.`.

<Note>
  Invoice and subscription webhooks share the same signing scheme but have **different headers, retry policies, and persistence behaviour**. The sections below call out where they diverge.
</Note>

## Request headers

### Invoice webhooks

| Header                | Description                                                                                                                    |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `X-Webhook-Signature` | Stripe-style signature: `t=<unix_seconds>,v1=<hex>`. The `v1` portion is HMAC-SHA256 over `<timestamp>.<rawRequestBody>`.      |
| `X-Webhook-Event`     | Event type string (e.g. `invoice.settled`)                                                                                     |
| `X-Webhook-Event-Id`  | Unique event ID (same as `eventId` in the body)                                                                                |
| `X-Webhook-Timestamp` | Unix seconds (string, e.g. `"1777623628"`) — informational only; verification uses the `t=` value inside `X-Webhook-Signature` |

### Subscription webhooks

| Header                | Description                                                                   |
| --------------------- | ----------------------------------------------------------------------------- |
| `X-Webhook-Signature` | Same scheme as invoice webhooks — verify the same way                         |
| `X-Settlx-Event`      | Event type string (e.g. `subscriber.activated`) — note: NOT `X-Webhook-Event` |
| `X-Settlx-Delivery`   | Unique delivery ID for this attempt (e.g. `sub_1777623628000_<subscriberId>`) |
| `X-Webhook-Timestamp` | Unix seconds (string)                                                         |

<Warning>
  If you filter incoming webhooks on the `X-Webhook-Event` header, you will silently drop subscription events. Either also check `X-Settlx-Event`, or use the top-level `event` field in the parsed JSON body — that field is consistent across both webhook types.
</Warning>

## Idempotency

| Webhook type        | Idempotency key                                                                                                                                                                              |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Invoice events      | `eventId` field in the body — stable across retries                                                                                                                                          |
| Subscription events | **No `eventId`** is sent. Use `subscriberId + event + timestamp` to deduplicate, or just make your handler idempotent on the destination state (e.g. "is the subscription already active?"). |

## Signature verification

Every delivery includes an `X-Webhook-Signature` header. You must verify it before processing any event. See [Webhook Integration](/webhooks/integration) for full verification examples in Node.js, Python, PHP, and Go.

## Retry policy

The retry policy differs by webhook type.

### Invoice webhooks

If your endpoint returns a non-`2xx` status or does not respond within 30 seconds, Settlx retries up to 10 times with exponential backoff:

| Attempt | Delay after previous failure |
| ------- | ---------------------------- |
| 1       | 1 minute                     |
| 2       | 5 minutes                    |
| 3       | 15 minutes                   |
| 4       | 1 hour                       |
| 5       | 6 hours                      |
| 6–10    | 24 hours each                |

After 10 failed attempts the delivery is marked permanently failed. Failed and pending deliveries appear in **Webhook Logs** in the dashboard, where you can inspect the payload and manually retry.

### Subscription webhooks

Subscription webhooks use a much shorter retry budget — **3 attempts** with **1s and 2s** backoff, \~10 second per-attempt timeout. They are **not persisted** to the dashboard's Webhook Logs. If your endpoint is down for more than a few seconds, the subscription event is lost and cannot be replayed.

If reliability matters more than latency, run an idempotent reconciliation job that periodically pulls subscriber state via the API and brings your local cache up to date.

## Response requirements

Your endpoint must return a `2xx` HTTP status within **30 seconds** (invoice) / **10 seconds** (subscription). Any `2xx` code is accepted — `200`, `201`, and `204` all count as successful delivery.

## Delivery status (invoice webhooks only)

The `webhook_logs.status` field can be one of:

| Status      | Meaning                                                 |
| ----------- | ------------------------------------------------------- |
| `pending`   | Delivery is queued or scheduled for retry               |
| `delivered` | Endpoint returned `2xx` — no further attempts           |
| `failed`    | All 10 attempts exhausted — delivery permanently failed |

Subscription webhooks do not have a status field because they are not persisted.

## Testing webhooks

Use the [Test Webhook](/api-reference/webhooks/test) endpoint to send a simulated event to your URL without waiting for a real payment. Useful for verifying your handler logic and signature verification during development.

<Note>
  Test webhooks do **not** appear in the dashboard's Webhook Logs view. They are sent ad-hoc and not persisted. Real events fired by actual invoice or subscription state changes are logged.
</Note>
