Stripe "cancel_at_period_end" set but the customer was still invoiced
Diagnoses unexpected Stripe invoices on subscriptions flagged cancel_at_period_end: which invoice lines are legitimate final-period charges (pending items, unbilled metered usage) versus real problems (a second subscription, a schedule recreating billing). Use when a canceled subscription still produces an invoice. Not for immediate cancellations or proration disputes.
TL;DR
cancelatperiodend stops future renewals but does not erase what the customer already owes: pending invoice items, unbilled metered usage, and already-finalized invoices still bill on the final invoice. Before calling it a bug, read the invoice lines and check billingreason. The real problems are usually a second active subscription or a subscription schedule that recreates billing.
The query
Stripe "cancel_at_period_end" set but the customer was still invoicedUse this when
- A subscription flagged cancelatperiod_end still produced an invoice
- Finance asks why a "canceled" customer was charged
- An agent set the cancel flag and the customer is disputing the charge
- You need to tell legitimate final charges apart from a real bug
Not for
- Cancel-now flows where you expected proration (different problem)
- Disputing the amount of a legitimate final invoice
- Trialing subscriptions that converted to paid (check status first)
Steps
1. Retrieve the subscription and confirm the flag is real
Fetch the subscription and check that cancelatperiodend is true, status is active, and canceledat is empty. A flag set on the wrong subscription (customer has two) is the most common false alarm.
Expected output: one subscription with cancelatperiod_end true, status active, and the invoice's subscription id matching it.
2. Read the invoice lines, not just the total
Open the unexpected invoice and read every line item. Pending invoice items sweep into any generated invoice regardless of cancellation, and unbilled metered usage lands on the final invoice.
Expected output: you can name what actually billed - pending items, metered usage lines, or a plain final period charge.
3. Check billing_reason on the invoice
A billingreason of subscriptioncycle for the final period is the expected last invoice. subscription_update points at a proration from a mid-period change. manual means someone created it by hand.
Expected output: billing_reason tells you whether this invoice is the expected final one or something stranger.
4. Rule out a second subscription or a schedule
List the customer's other subscriptions: the flag is per subscription, not per customer, so a second active subscription bills normally. Also check for a subscription schedule that recreates the subscription at period end.
Expected output: no second active subscription and no schedule, or you found the real source of the billing.
5. Confirm the customer.subscription.deleted webhook arrived
After the period ends, Stripe sends customer.subscription.deleted. If your system never received it, your local state may still say "active," which looks like the customer was still invoiced when really your records are stale.
Expected output: webhook received and local state matches Stripe.
6. Going forward, clear the slate at cancellation time
When setting cancelatperiod_end, first invoice or delete pending invoice items deliberately, and review unbilled metered usage so the final invoice is expected, not a surprise.
Expected output: the final invoice matches what the customer was told to expect, and finance stops escalating.
Variant phrasings
cancelatperiod_end still charged stripe
Same as steps 2 and 3. Most of these are the legitimate final-period invoice.
subscription canceled but invoiced final period
The final period invoice is expected behavior; step 3 confirms it is actually the final one.
pending invoice items after cancel
Same as step 2: pending items always sweep into the next generated invoice, cancellation or not.
Why it happens
cancelatperiod_end is a renewal gate, not a billing eraser: it says do not start another period. Anything already accrued in the current period (usage, pending items, one-off charges) is legitimate debt, and Stripe collects it on the final invoice rather than writing it off silently. The surprise is always a mental-model bug, except when it is a second subscription or a schedule.
Edge cases
- Trialing subscriptions: if the trial already ended before the flag was set, the subscription converts to paid first; check status, not just the flag.
- Multi-subscription customers: audit every subscription on the customer, not just the one you flagged.
- Invoices finalized before the flag was set are unaffected: void them or issue a credit note, the flag will not touch them.
- Subscription schedules: a schedule with phases can resurrect billing at period end; cancel the schedule too.
- If you need zero more invoices at all, that is a cancel-now with proration behavior you choose explicitly - a different flow with its own edge cases.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_Bi53R1G076-SzDKbfCgrDw
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.