When Stripe is the right gateway
Stripe's documentation, subscription infrastructure, and webhook reliability are ahead of every other gateway. If your users are in the US, UK, or Europe — or you're building a B2B SaaS where complex billing matters (trials, proration, usage-based pricing) — Stripe is the right foundation. Just don't expect it to handle Nigerian or Ghanaian cards reliably. It doesn't, and it's not trying to. For African markets, Paystack or Flutterwave belong in the same conversation.
How Stripe Checkout works
Your server creates a Checkout Session via the Stripe API — Stripe returns a URL for the hosted checkout page. You redirect the user there. They pay. Stripe sends them back to your success URL. You verify via webhook before fulfilling. The session handles everything in between: card validation, 3D Secure, Apple Pay, Google Pay, local payment methods by region.
1. Get your API keys
From the Stripe dashboard under Developers > API keys. The secret key belongs on the server only:
# Test keys — safe for development STRIPE_SECRET_KEY=sk_test_... STRIPE_PUBLISHABLE_KEY=pk_test_... STRIPE_WEBHOOK_SECRET=whsec_... # From Stripe dashboard > Webhooks
2. Create a Checkout Session (server-side)
Plain HTTP — works in any language. The Stripe SDK is convenient but optional:
async function createStripeCheckoutSession({ priceId, customerEmail }) {
const response = await fetch('https://api.stripe.com/v1/checkout/sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
mode: 'payment',
'line_items[0][price]': priceId,
'line_items[0][quantity]': '1',
success_url: `${process.env.APP_URL}/payment/success`,
cancel_url: `${process.env.APP_URL}/pricing`,
customer_email: customerEmail,
}),
});
const session = await response.json();
if (!session.url) throw new Error('Stripe session creation failed');
// Redirect user to session.url
return session.url;
}3. Verify webhooks — this is not optional
A user can hit your success URL without paying. Never fulfil an order based on the redirect alone. Stripe signs every webhook event — verify the signature before acting on it:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
async function handleStripeWebhook(rawBody, signatureHeader) {
const event = stripe.webhooks.constructEvent(
rawBody,
signatureHeader,
process.env.STRIPE_WEBHOOK_SECRET
);
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
// Fulfil using session.id as idempotency key
}
return { received: true };
}4. Subscriptions with Stripe Checkout
Switch mode from 'payment' to 'subscription' and pass a recurring price ID — Stripe handles the rest:
// Same function as above, just change mode
async function createStripeSubscriptionSession({ priceId, customerEmail }) {
const response = await fetch('https://api.stripe.com/v1/checkout/sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
mode: 'subscription', // Changed from 'payment'
'line_items[0][price]': priceId, // Must be a recurring price
'line_items[0][quantity]': '1',
success_url: `${process.env.APP_URL}/dashboard`,
cancel_url: `${process.env.APP_URL}/pricing`,
customer_email: customerEmail,
}),
});
const session = await response.json();
return session.url;
// Listen for: customer.subscription.created, invoice.payment_failed
}Test card numbers
Use these with any future expiry and any 3-digit CVV:
- 4242 4242 4242 4242 — payment succeeds
- 4000 0000 0000 9995 — card declined
- 4000 0025 0000 3155 — requires 3D Secure authentication
- 4000 0000 0000 0341 — attaches successfully but payment fails
- Run stripe listen --forward-to localhost:3000/your-webhook-path for local webhook testing
Common questions
Need payments that work for your market — not just the US?
We build payment integrations that fit where your users actually are — Stripe for global, Paystack for Nigeria, Flutterwave for the wider continent. Tell us your stack and your markets.
Continue reading