Billing
Autumn Credit Billing
Use when adding Autumn, a billing layer in front of Stripe, to sell a credit balance shared across apps. Covers using the Clerk userId as the Autumn customer id, hosted checkout, verifying Svix-signed webhooks, and granting credits only after re-verifying the purchase. Also use when a webhook grants credits it should not, or a purchase completes without the balance moving. If money buys credits, use this skill.
1 file
Description
Use when adding Autumn, a billing layer in front of Stripe, to sell a credit balance shared across apps. Covers using the Clerk userId as the Autumn customer id, hosted checkout, verifying Svix-signed webhooks, and granting credits only after re-verifying the purchase. Also use when a webhook grants credits it should not, or a purchase completes without the balance moving. If money buys credits, use this skill.
name: autumn-credit-billing description: Use when adding Autumn (a billing layer in front of Stripe) to sell a universal credit balance shared across apps. Covers using the Clerk userId as the Autumn customer id, hosted checkout, verifying Svix-signed webhooks, and granting credits only after re-verifying the purchase.
Autumn Credit Billing
When to trigger
Selling credits (not per-app subscriptions) where one purchase should spend across every app you run. Autumn owns the purchase side; your own store owns the spendable balance.
Split the responsibilities
- Autumn + Stripe: checkout, payment, the record that a customer bought a product.
- Your database (for example Neon): the spendable balance. Autumn never mutates it.
- The webhook route composes the two: verify the event, then grant credits locally.
Use the Clerk userId as the customer id
Set the Autumn customer id to the Clerk userId. Because that id is stable across every app, a customer buys once and spends the same balance everywhere. Do not mint per-app customer records.
// createCheckout: Clerk userId + product id -> hosted Stripe URL
const res = await autumnFetch("/checkout", {
method: "POST",
body: JSON.stringify({ customer_id: clerkUserId, product_id: productId, success_url, cancel_url }),
});
Verify the webhook, then re-confirm before granting
Autumn signs deliveries with Svix (svix-id, svix-timestamp, svix-signature). Verify the signature first. Then re-fetch the customer from Autumn to confirm they actually own the product before you grant credits. Never grant on the webhook payload alone; a forged or replayed body must not mint credits.
if (!verifyAutumnWebhook(headers, rawBody)) return new Response("bad sig", { status: 400 });
if (await customerHasProduct(customerId, productId)) await grant(customerId, credits);
Pitfalls
- Granting straight from the webhook body without re-verifying against Autumn trusts an unauthenticated payload. Confirm against the source of truth.
- Verifying the signature over a parsed/re-serialized body fails; Svix signs the raw bytes. Read the raw request body for verification.
- Creating a separate customer per app breaks the "buy once, spend everywhere" model. Key every app on the same Clerk userId.
- Reading
AUTUMN_SECRET_KEYat module load can crash a build without the env. Read it at call time and never log it.
Added 2026-07-01. Back to the Skill Library.

Get Smarter About AI Dev
New tutorials, open-source projects, and deep dives on coding agents - delivered weekly.