Pinned API version
billing.config.ts pins STRIPE_API_VERSION so a dashboard default
bump can't ship silent breaking changes (the Dahlia release moved
invoice.subscription and relocated billing periods onto subscription
items). To upgrade: read the Stripe API changelog, bump the constant,
run the billing specs, move the webhook endpoint to the same version
(below), exercise the dev loop once.
The pin covers only the calls the backend makes. Stripe renders every
webhook payload at the webhook endpoint's API version, or at the
account default when the endpoint has none, whatever this constant says.
So the handler meets two versions: the subscription inside a
customer.subscription.* payload (its status, customer,
metadata.userId, cancel_at_period_end, and the first item's price.product
and current_period_start/current_period_end) arrives at the
endpoint's version, while the subscription it retrieves after
checkout.session.completed arrives at the pinned one. Keep the endpoint
on STRIPE_API_VERSION and move it with every bump.
The backend verifies against exactly one STRIPE_WEBHOOK_SECRET, and a
new endpoint has a new secret, so move the endpoint like this, in each
mode you use (test and live endpoints are separate):
- Developers → Webhooks → Add endpoint with the same URL, the same
eight events, and API version set to the new
STRIPE_API_VERSION. - Put the new endpoint's
whsec_…intoSTRIPE_WEBHOOK_SECRETand redeploy. - Delete the old endpoint.
Between 1 and 2 the new endpoint's deliveries fail verification; between
2 and 3 the old one's do. Both are harmless: every event goes to both
endpoints with the same event id, the one that verifies applies it, and
the failed copies Stripe retries later are no-ops. The subscription row's
cursor skips an event id it already applied, the payment hook runs once
per event id (billing_payment_hook_claims), and the dispute lock is
idempotent. Keep both windows short anyway, since a failing endpoint
fills the Webhooks page with errors and earns alert emails. Whether you
deploy the code first or move the endpoint first does not matter when
the fields above are the same in both versions, as they are from
2026-05-27.dahlia to 2026-09-30.endive; the only coupling is that the
new secret ships with, or right after, the new endpoint.
The SDK version and this constant move together. stripe's own types
name the API version they were generated against, so upgrading the
package without the constant fails the build with
Type '"2026-05-27.dahlia"' is not assignable to type '"2026-09-30.endive"'
That error is the reminder, not a problem to route around. Do not pin the package to a patch range to silence it: the committed lockfile already gives every fresh clone the same version, so narrowing the range only hides the prompt on the one occasion you want it.