Subscription Webhooks
Set a notifyUrl when you create a subscription, and Confirmo POSTs a JSON envelope to that URL for every lifecycle and payment event.
The event envelope
Every event shares one envelope. The data object has the same shape as the matching REST resource: a Subscription for lifecycle events, a Payment for payment events. So you rarely need a follow-up call.
{
"id": "01977f2a-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
"type": "subscription.payment.succeeded",
"timestamp": "2026-06-29T10:30:00Z",
"sequence": 4,
"resourceType": "subscription",
"resourceId": "5c2e8d4a-7f1b-4e3c-8a9d-6b0f2c4e8a1d",
"data": { "...Payment or Subscription object..." }
}Two fields do the work in your handler: id (unique per event — dedupe on it) and sequence (goes up per subscription — apply events in order). resourceId is always the subscription ID, even for payment events.
The events
The two events most integrations act on first:
subscription.activated— the subscriber completed checkout and the first payment confirmed. Provision access.subscription.payment.succeeded— a recurring charge settled. Extend access for the new period.
Here's the full list:
| Event | Sent when | What you do |
|---|---|---|
subscription.created | A subscription is created, usually in PENDING. | Nothing, informational. |
subscription.activated | Subscription checkout is done, and billing is on schedule. | Provision access. |
subscription.past_due | A charge failed and the subscription entered PAST_DUE. | Prompt the subscriber to top up. |
subscription.resumed | A PAST_DUE subscription recovered within the configured grace period. | Restore full access. |
subscription.canceled | Canceled before its natural end. See data.cancellation.reason. | Turn off access. |
subscription.expired | Ended naturally or after a terminal failure. See data.expiration.reason. | Turn off access. |
subscription.payment.succeeded | A recurring charge settled. | Extend access for the new period. |
subscription.payment.failed | A recurring charge failed. See data.failure.reason. | Note the reason; expect past_due. |
Handling webhooks safely
- Reply fast. Return any
2xxas soon as you've stored the event, then do the real work in the background. A non-2xxor a timeout means we retry. - Dedupe on
id. The same event can arrive more than once. Process eachidonly once. - Apply in order using
sequence. Events for one subscription carry an increasingsequence. Apply an event only if it's newer than the last one you handled, and ignore older arrivals.
Always verify a webhook before you trust itRequests are signed with Ed25519. Verify the signature against the raw request body, and reject anything older than the replay window. The exact signing string, JWKS endpoint, retry schedule, rate limits, and redirect rules are all in the Subscriptions API Specification — build your verification against that.

