add recurring subscription billing with stripe: customer, checkout, webhook reconciliation, idempotency. trigger when a builder says "add subscription billing", "charge customers monthly", or "set up stripe subscriptions".
---
description: add recurring subscription billing with stripe. customer, checkout, webhook reconciliation, idempotency. trigger on "add subscription billing", "charge customers monthly", "set up stripe subscriptions".
---
# stripe subscription billing
stand up monthly subscription billing without hallucinating the webhook path. uses the verified @stripe/stripe-node module.
## intent
take a builder from "i need to charge customers monthly" to a working subscription + webhook flow that reconciles state correctly. the failure mode this prevents: a generated billing flow that never verifies the webhook signature, so payment state silently drifts.
## inputs
- a stripe account with a price id for the plan
- STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET in env
- a server route that can receive the webhook (raw body, not parsed json)
## procedure
### step 1, create the customer
```js
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const customer = await stripe.customers.create({ email });
```
### step 2, start the subscription via checkout
use checkout for the first subscription, it handles SCA + card collection.
```js
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
customer: customer.id,
line_items: [{ price: PRICE_ID, quantity: 1 }],
success_url, cancel_url,
});
```
### step 3, verify and handle the webhook (the part the model skips)
the raw body and the signature header are mandatory. never trust an unverified webhook.
```js
const event = stripe.webhooks.constructEvent(rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET);
if (event.type === 'invoice.paid') { /* mark active */ }
if (event.type === 'customer.subscription.deleted') { /* mark canceled */ }
```
### step 4, make it idempotent
webhooks retry. dedupe on event.id before mutating state.
## decision points
- test vs live: keys are environment-scoped, never mix them.
- proration: set proration_behavior explicitly on plan changes, the default surprises people.
- trials: trial_period_days on the subscription, not a manual timer.
## output contract
a subscription row in your db driven by webhook events, an idempotent handler, and signature verification on every webhook. no payment state set from client-side success_url alone.
## outcome signal
success means a real test-mode subscription advances from incomplete to active to canceled purely from webhook events, with zero state set client-side. if state ever changes without a verified webhook, the flow is wrong.
don't have the plugin yet? install it then click "run inline in claude" again.
added explicit setup guidance for environment variables and raw body handling, detailed decision points for test vs live keys and webhook retries, expanded output contract with required db fields, edge cases including rate limits and auth scenarios, and idempotency step broken out with atomic storage requirement.
stand up monthly subscription billing without hallucinating the webhook path. uses the verified @stripe/stripe-node module.
take a builder from "i need to charge customers monthly" to a working subscription and webhook flow that reconciles state correctly. the failure mode this prevents: a generated billing flow that never verifies the webhook signature, so payment state silently drifts out of sync with stripe's truth. use this skill when a builder says "add subscription billing", "charge customers monthly", or "set up stripe subscriptions".
create the stripe customer
create a checkout session for subscription
set up webhook endpoint (raw body required)
verify and process webhook events
implement idempotency guard
success is a subscription row in your database with these fields:
the webhook handler must verify the signature with constructEvent() before any logic. no payment state set from client-side success_url alone. all state mutations must be idempotent and driven by verified events.
success means a test-mode subscription created via checkout advances from incomplete (before payment) to active (after invoice.paid webhook) to canceled (after customer.subscription.deleted webhook) purely from webhook events, with zero state set client-side. if you see a subscription status change without a corresponding verified webhook event in your logs, the flow is broken. test by creating a subscription in test mode, viewing the webhook delivery logs in stripe dashboard, and confirming each event matches a db mutation with the same event.id.
credits: original skill by implexa.