# Webhooks for subscriptions

**Receive real-time notifications when subscription states change - and know which changes to poll for instead.**

Webhook events push subscription state changes to your server as they happen, so you can provision access, trigger dunning flows, and keep your records in sync without polling. 

The setup flow - registering the webhook URL, subscribing to events, verifying payload signatures - is shared with the rest of the Merchant API. The rest of this guide covers what's specific to subscriptions.

## How it works

Revolut sends each event as an HTTP POST request to the `url` you registered with [Create a webhook](/docs/api/merchant#create-webhook). The following events are sent for subscriptions:

| Event | Fires when | Typical reaction |
|-------|------------|------------------|
| `SUBSCRIPTION_INITIATED` | The subscription is created | Mark the customer's account as provisioning - access starts once the subscription activates |
| `SUBSCRIPTION_FINISHED` | The subscription completes its last cycle naturally | End access gracefully - the customer completed their commitment |
| `SUBSCRIPTION_CANCELLED` | The subscription is cancelled - by you or the customer | Stop provisioning immediately, record the cancellation |
| `SUBSCRIPTION_OVERDUE` | A renewal or settlement payment fails | Trigger your dunning flow - notify the customer, consider restricting access |

The payload is minimal by design - the [Webhook event](/docs/api/merchant#webhook-webhook-event) API reference documents the exact shape you'll receive:

- ![Request]
  ```http
  POST /your-webhook-endpoint HTTP/1.1
  Host: your-server.example.com
  Content-Type: application/json
  Revolut-Request-Timestamp: 1721050064
  Revolut-Signature: v1=4fce70bda66b2e713be09fbb7ab1b31b0c8976ea4eeb01b2

  {
    "event": "SUBSCRIPTION_OVERDUE",
    "subscription_id": "550e8400-e29b-41d4-a716-446655440000",
    "external_reference": "cus_8f3a91"
  }
  ```

  | Parameter | Description |
  |-----------|-------------|
  | `event` | The event type - one of the subscription events above. |
  | `subscription_id` | The affected subscription - use it to [Retrieve a subscription](/docs/api/merchant#retrieve-subscription) for the full current state. |
  | `external_reference` | Your identifier, echoed from creation. Optional - match it to your records instead of the UUID if you prefer. |

- ![Response]
  Acknowledge quickly, then process:

  ```http
  HTTP/1.1 204 No Content
  ```

:::note[Payloads are pointers, not state snapshots]
The payload tells you *that* something changed, not the full subscription state. 

Always retrieve the subscription before acting on an event - the state may have moved on since the event was generated.
:::

### Before you begin

Before you start, make sure you have the following:

- [ ] **Your subscription model implemented** - you've built the subscription you want to monitor with webhooks - see [Get started with the Subscriptions API](/docs/guides/merchant/billing-subscriptions/api/get-started)
- [ ] **A registered webhook URL** - subscribe it to the `SUBSCRIPTION_*` events - see [Use webhooks](/docs/guides/merchant/monitor-and-observe/webhooks/using-webhooks)
- [ ] **Signature verification ready** - see [Verify the payload signature](/docs/guides/merchant/monitor-and-observe/webhooks/verify-the-payload-signature)

## Handle subscription events

### Respond and retry

- **Acknowledge fast** - respond with any `200-399` code within the timeout (`204` is recommended) and do heavy processing off the request thread.
- **Retries** - if delivery times out or your endpoint responds `4XX`, Revolut retries the event **3 more times, each with a 10-minute delay**. Make your handler idempotent - the same event can arrive more than once.
- **Ordering isn't guaranteed** - handle events based on the subscription's current retrieved state rather than assuming events arrive in lifecycle order.
- **IP allowlisting** - events originate from these addresses:

| Environment | Webhook IP addresses |
|-------------|---------------------|
| Production | `35.246.21.235`, `34.89.70.170` |
| Sandbox | `35.242.130.242`, `35.242.162.241` |

### Poll what webhooks don't tell you

Subscription webhooks cover a small set of state changes. Several lifecycle moments emit **no event** - track those by polling [Retrieve a subscription](/docs/api/merchant#retrieve-subscription) or the cycle list:

| Change | Event | How to track |
|--------|--------|--------------|
| Subscription created | `SUBSCRIPTION_INITIATED` | Webhook |
| Activation - first payment collected or payment method attached | None | Retrieve the subscription, check `state: "active"` |
| Plan change scheduled or applied | None | Retrieve the subscription |
| Cycle created or completed | None | [Retrieve a subscription cycle list](/docs/api/merchant#retrieve-subscription-cycle-list) |
| Usage settlement charged | None | Retrieve the cycle - the `post_billing_order_id` appears after the cutoff |
| Renewal or settlement payment failed | `SUBSCRIPTION_OVERDUE` | Webhook - see [Failed payments and retries](/docs/guides/merchant/billing-subscriptions/subscription-lifecycle#failed-payments-and-retries) |
| Cancellation | `SUBSCRIPTION_CANCELLED` | Webhook |
| Natural completion | `SUBSCRIPTION_FINISHED` | Webhook |

:::tip
For activation specifically, a practical pattern is to poll the subscription after the customer completes the setup order - there's no activation event to wait for.
:::

## Implementation checklist

Confirm your webhook integration handles subscription events correctly:

- [ ] Webhook registered and subscribed to the `SUBSCRIPTION_*` events - see [Use webhooks](/docs/guides/merchant/monitor-and-observe/webhooks/using-webhooks)
- [ ] Every payload's signature is verified before processing - see [Verify the payload signature](/docs/guides/merchant/monitor-and-observe/webhooks/verify-the-payload-signature)
- [ ] Handler responds `200-399` quickly and processes asynchronously
- [ ] Handler is idempotent - the 3 retries with 10-minute delays can deliver duplicates
- [ ] Events are matched to your records via `external_reference` or `subscription_id`
- [ ] `SUBSCRIPTION_OVERDUE` triggers your dunning flow; `SUBSCRIPTION_CANCELLED` and `SUBSCRIPTION_FINISHED` stop provisioning
- [ ] Activation, plan changes, and cycle completions are covered by polling - no events exist for them

<!--
TODO (PTTD-824)

1. RESOLVED 2026-09-08 per PO: anchor format confirmed - articles are stripped from operation-
   summary anchors (e.g. #create-subscription-plan, matching master-page precedent like #retrieve-
   order and #create-webhook). All links updated to the article-stripped form. Historical inventory:
   (create-a-webhook, retrieve-a-subscription, retrieve-a-subscription-cycle-list).

2. Event timing assumptions: SUBSCRIPTION_INITIATED firing at creation (not at activation) and
   SUBSCRIPTION_OVERDUE firing on payment failure with retries ongoing derive from the event names,
   Webhook-Subscription-Event.yaml, and the lifecycle page's overdue model - verify both in the
   sandbox.

3. Dunning deep dive deferred to the upcoming manage/ category - cross-link the SUBSCRIPTION_OVERDUE
   row when that page lands.
-->

## What's next

- [:Repayment: Build a fixed-rate subscription](/docs/guides/merchant/billing-subscriptions/api/fixed 'Charge a fixed recurring amount - the simplest subscription flow')
- [:PlaneSeat: Build a per-seat subscription](/docs/guides/merchant/billing-subscriptions/api/per-seat 'Bill per seat, with a stable or changing seat count')
- [:LimitHigh: Build a usage-based subscription](/docs/guides/merchant/billing-subscriptions/api/usage-based 'Meter usage, report it via the API, and settle charges at cycle end')
- [:Voucher: Build a subscription with trials and introductory pricing](/docs/guides/merchant/billing-subscriptions/api/trials 'Offer free trials and promotional pricing phases')