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 | Required confirmations reached, invoice ready for settlement. data.payment_quality distinguishes full from overpaid. |
invoice.underpaid | Customer sent less than the required amount — invoice stays open for top-up |
invoice.settled | Funds transferred to your settlement wallet — fulfil here |
invoice.expired | Invoice expired without full payment. data.expiry_reason is no_payment or underpaid_unresolved. |
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 | Customer enrolled — first invoice created, awaiting payment |
subscriber.activated | Payment confirmed — subscription is now active |
subscriber.past_due | New billing cycle started — renewal invoice created |
subscriber.expired | Grace period elapsed without payment |
subscriber.cancelled | Subscription cancelled by merchant |
subscriber.paused | Subscription paused by merchant |
subscriber.resumed | Paused subscription resumed |
Invoice events
Invoice events use a nested data envelope:
{
"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:
{
"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..
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.
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) |
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.
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 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 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.
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.