Status: available. Five subscription events, delivered to any endpoint you subscribe.
Why you need these
For a Gale-billed subscription, Gale is the only system that knows what happened. Gale holds the billing schedule, charges the stored card, and runs the retry policy when a payment fails. That has a consequence worth being explicit about: on the Elements + PaymentIntent path there is no Stripe subscription object, so Stripe will not notify you of a renewal. Nor will your payment processor — it only ever sees a series of unrelated card charges. If you don’t consume these webhooks, the only way to learn that a subscription renewed is to pollGET /v2/subscriptions.
For a physical-goods subscription that means nobody tells you to ship the next box.
The events
subscription.renewed is the fulfilment signal, and it covers the first cycle too. We deliberately don’t split sign-up from renewal into two events — you handle one, and every paid cycle arrives the same way. It fires only once the money is actually captured, not at authorisation, so it always means funds moved.
Two things worth knowing:
- A scheduled cancellation does not fire
subscription.canceled. When a shopper cancels at period end, the subscription stays active and billable until that date. You get the event when it actually ends. Until thencancel_at_period_endistrueon the subscription body. - Not every subscription emits every event. If your subscription is scheduled by Stripe rather than by Gale, Gale never runs its renewal engine for it, so you’ll see
created/canceled/resumedbut notrenewedorpayment_failed— Stripe reports those to you directly.
Payload
Every event uses the same envelope:data is the same subscription object you get from GET /v2/subscriptions/{id} — minus the cycles array, so the body stays small — plus payment_method_type:
plan_id is the plan the subscription was created from, and it is provenance, not a live pointer — amount, interval and interval_count on the subscription stay authoritative, so editing a plan never reprices a subscription already running on it. It is null on a subscription created before plans existed.
subscription.renewed adds invoice
invoice_id is the cycle’s public id, the same value GET /v2/subscriptions/{id} returns for that cycle and the same field subscription.disputed carries — so you can fetch it directly at GET /v2/subscriptions/{id}/invoices/{invoiceId}.
order_id is a real Gale order — the same identifier the order and refund endpoints take, so you can reconcile a cycle against an order without a second lookup.
subscription.payment_failed adds failure
disposition — it tells you what Gale is going to do next, so you don’t have to interpret raw decline codes:
status carries the resulting subscription state (past_due, action_required, or unpaid). Once retries are exhausted you’ll receive a final payment_failed with status: "unpaid" — that’s the end of the dunning cycle, and no further attempt is coming.
subscription.disputed adds dispute
A cardholder disputed one of your cycles. One event covers the whole dispute
lifecycle — branch on stage rather than subscribing to several events.
The subscription’s
status becomes disputed while the hold stands, and returns to exactly the status it held before once the dispute is reversed. That is the value you will see on GET /v2/subscriptions and in the data of this event.
A confirmed dispute stops billing, and that is reversible. Gale will not
charge the next cycle while a dispute stands, because charging a card whose
holder is disputing the last charge tends to produce more disputes. If the
dispute is later reversed, the subscription returns to exactly the state it was
in before — including a mid-dunning state — and renewals continue on the
original schedule. You don’t have to call anything to resume it.
invoice_id identifies which cycle was disputed, and matches the
invoice_id on GET /v2/subscriptions/{id}, so you can reconcile it against
the cycle you fulfilled. amount is in minor units (cents).
billing_held is the field to branch on if you only care about the billing
consequence — stage tells you where the dispute is, billing_held tells you
what Gale did about it.
event_code is normally the card-network event as Adyen reported it. One value
is not: MANUAL_RELEASE means Gale support lifted the hold by hand, for the
case where a dispute was resolved outside the normal notification flow. It
arrives as stage: "reversed" with billing_held: false, because the billing
consequence is identical — billing has restarted. If you match on Adyen’s codes,
treat anything you don’t recognise as “read billing_held”.
Receiving them
1. Register your endpoint
Add it in the Gale merchant dashboard under Webhooks, choosing the subscription events you want. Endpoint registration isn’t part of the merchant API — the dashboard (or your Gale contact) is where it happens. You’ll get a signing secret when the endpoint is created. Store it; you’ll need it in step 2.2. Verify the request came from Gale
Every delivery is signed. The headers you get:v1 is HMAC-SHA256 over "<t>.<raw request body>", keyed with your endpoint secret.
timingSafeEqual), and check the lengths first — timingSafeEqual throws on a length mismatch.
Why there can be more than one v1
While you’re rotating your secret, we sign with both the new secret and the outgoing one for 24 hours. Accept the delivery if any v1 matches. That’s what lets you call rotate-secret, deploy the new secret at your own pace, and never drop a webhook in between.
The timestamp is what stops replays
t is inside the signed string, so an attacker can’t shift it without breaking v1. Rejecting anything outside your window is therefore a real replay defence — but only outside the window. Inside it, dedupe on X-Gale-Delivery (see below).
Secrets issued after 20 August 2026 start with
whsec_. The prefix is part of the key — pass the whole string to your HMAC. Older secrets keep working as-is.3. Respond quickly, then work
Return a 2xx as soon as you’ve stored the event. Any non-2xx is treated as a failure and the delivery is retried. Requests time out after 10 seconds — do fulfilment work on your own queue, not inside the request.4. Deduplicate
Retries mean you can receive the same event more than once, and a slow response that we time out may still have reached you. Bothid (the evt_… in the body) and X-Gale-Delivery are unique — key your processing off one of them and ignore repeats.
Events can also arrive out of order. Don’t infer state from arrival sequence; use status and next_charge_at in the body, which always reflect the subscription at the moment the event was raised.
Retries
A failed delivery is attempted 5 times in total — the first try plus four retries:dead_letter and abandoned. That gives you a window of about 2 h 36 min: an outage shorter than that loses nothing, a longer one drops events, so reconcile with GET /v2/subscriptions after an extended incident.
Disabling or deleting an endpoint dead-letters anything still queued for it.
Testing
Point an endpoint at a request-capture service, start a subscription in test mode, and you’ll seesubscription.created immediately and subscription.renewed once the sign-up charge captures. To exercise the failure path, use a card that produces a decline — the resulting disposition tells you which branch of your handler ran.
Related resources
Custom Payment Methods
Which of the two Stripe integration paths fits.
Elements + PaymentIntent
Your backend hands off to Gale; Stripe is never in the payment path.
Subscription Plans
Attach a recurring price and cadence to your products.
Elements + Checkout Session
Stripe orchestrates the charge and owns the subscription object.
