Stripe
Wire Stripe Checkout, subscriptions, and webhooks into your FlareX app — from API keys to first paid customer.
Updated
Stripe is the most common integration on FlareX. This page covers the full flow: API keys, Checkout for one-time payments, subscription plans, and the webhook plumbing that keeps your app's state in sync with Stripe.
API keys
Two keys, two environments:
| Mode | Key prefix | Where to find |
|---|---|---|
| Test | sk_test_… | Dashboard top-right toggle "Test mode" → Developers → API keys |
| Live | sk_live_… | Same place, "Test mode" off |
Always start in test mode. Flip to live keys only when you've confirmed the full flow works end-to-end.
Add to Secrets as STRIPE_SECRET_KEY. Don't ship the publishable key (pk_...) anywhere private — that one's safe in the browser.
Pattern 1: Checkout for one-time payments
Simplest possible payment flow. User clicks a button → redirected to Stripe-hosted checkout → redirected back when done.
Add Stripe Checkout. Use STRIPE_SECRET_KEY from Secrets.
POST /checkout creates a Checkout Session for the requested SKU and
returns the session URL. Frontend redirects the user to it.
success_url: /thanks?session_id={CHECKOUT_SESSION_ID}
cancel_url: /pricing
After redirect to /thanks, look up the session, confirm payment_status
is "paid", and render a confirmation page.
FlareX writes:
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{ price: 'price_…', quantity: 1 }],
success_url: `${BASE_URL}/thanks?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${BASE_URL}/pricing`,
});
return { url: session.url };
Pattern 2: Subscriptions
Same shape, mode: 'subscription'. The user gets a recurring charge; you get a customer.subscription.created webhook to mirror state into your DB.
Add subscription Checkout. Plans:
Starter: $9/mo → STRIPE_PRICE_STARTER
Pro: $29/mo → STRIPE_PRICE_PRO
POST /subscribe takes a plan code, creates the session, returns the URL.
Mirror subscription state into a `subscriptions` table keyed by user_id.
Update the row on customer.subscription.created/updated/deleted webhooks.
Pattern 3: Webhooks
Stripe is the source of truth for billing state. Your DB is a mirror. Webhooks keep them in sync.
The minimum events:
checkout.session.completed— fires when Checkout finishescustomer.subscription.created/updated/deleted— subscription lifecycleinvoice.paid/invoice.payment_failed— for dunning
See the webhooks doc for the signature verification pattern. The Stripe-specific bit:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
app.post('/webhooks/stripe', { config: { rawBody: true } }, async (req, reply) => {
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
req.rawBody,
req.headers['stripe-signature'] as string,
process.env.STRIPE_WEBHOOK_SECRET!,
);
} catch {
return reply.code(400).send();
}
switch (event.type) {
case 'checkout.session.completed':
// mark order as paid
break;
case 'customer.subscription.updated':
// mirror sub state
break;
}
reply.send({ received: true });
});
The webhook secret (whsec_…) is per endpoint, not per account. Test-mode and live-mode have separate webhook endpoints with separate secrets. When you flip to live, register a new endpoint and store the new secret as STRIPE_WEBHOOK_SECRET.
Pattern 4: Customer portal
Don't write a billing UI yourself. Stripe ships one:
Add a /billing-portal endpoint. Look up the user's stripe_customer_id,
create a billingPortal session, return the URL. Frontend redirects to
it. Users can update payment method, cancel, view invoices.
const session = await stripe.billingPortal.sessions.create({
customer: stripeCustomerId,
return_url: `${BASE_URL}/account`,
});
return { url: session.url };
Idempotency
Stripe accepts an idempotency-key header on every mutation. If you hit a network blip mid-charge, retry with the same key and Stripe returns the original result instead of double-charging:
await stripe.charges.create(
{ amount: 999, currency: 'usd', customer: cusId },
{ idempotencyKey: orderId },
);
FlareX picks idempotency keys for you (typically the order id or a UUID per attempt). Mention idempotency explicitly if you have a custom requirement.
Test cards
Use these in test mode to exercise specific flows:
| Card | Behaviour |
|---|---|
4242 4242 4242 4242 | Always succeeds |
4000 0000 0000 0002 | Always declined |
4000 0000 0000 9995 | Insufficient funds |
4000 0027 6000 3184 | Requires 3DS authentication |
Any future expiry, any CVC, any ZIP.
Going live
Replace test keys with live keys
Update
STRIPE_SECRET_KEYin Secrets. Restart the app.Register live-mode prices
Live and test prices are separate. Recreate the products + prices in live mode and update
STRIPE_PRICE_*env vars.Register a live webhook endpoint
Same URL, but in live-mode dashboard. Copy the new
whsec_…intoSTRIPE_WEBHOOK_SECRET.Smoke test with a real card
Create a Stripe coupon for 100% off, run yourself through the live flow once, then disable the coupon.
What's next
- Webhooks — signature verification, idempotency, retries
- 3rd-party APIs overview — error handling and rate limits
- OpenAI — common second integration after Stripe