Premium Website Templates — Designer-Level UI for Modern Brands | ProofMatcher

Stripe Payment Integration: Complete Developer Guide (2026)

Stripe in 2026: The Definitive Payment Platform

Stripe has maintained its position as the best payment platform for developers in 2026. The API design is exceptional, the documentation is the industry standard for developer documentation quality, the test mode enables full payment flow testing without real money, and the product surface has expanded to cover virtually every payment scenario: one-time payments, subscriptions, usage-based billing, marketplace payouts, B2B invoicing, and embedded finance. For any SaaS or e-commerce product, Stripe is the correct choice unless operating in markets where Stripe doesn't support payouts.

Checkout Sessions: The Right Starting Point

Stripe Checkout is a Stripe-hosted payment page that handles card validation, 3D Secure authentication, Apple Pay, Google Pay, and BNPL options automatically. It's the fastest path to accepting payments securely — you never handle raw card data. Create a Checkout Session on the server with the line items, success URL, and cancel URL, then redirect the customer to session.url. After payment, Stripe redirects to your success URL with the session ID. Verify the payment by retrieving the session server-side — never trust client-side success redirects without server verification.

Webhooks: The Critical Missing Piece

Webhooks are how Stripe notifies your server of asynchronous events — payment completion, subscription renewal, payment failure, refund processed. Many developers build the happy path (checkout session completes → grant access) but miss the webhook handling that makes the system reliable. Critical webhooks to handle: checkout.session.completed (payment succeeded), payment_intent.payment_failed (payment failed), customer.subscription.deleted (subscription cancelled), invoice.payment_failed (subscription renewal failed). Always verify webhook signatures using Stripe's webhook secret — never process webhooks without signature verification.

Subscriptions with Customer Portal

Stripe Billing handles recurring subscriptions with automatic retry logic for failed payments (Smart Retries). Create a Customer, attach a Payment Method, then create a Subscription with the price ID. Stripe's Customer Portal — a hosted page where customers can manage their subscription, update payment methods, and cancel — eliminates the need to build subscription management UI. Create a portal session server-side and redirect the customer to the portal URL. Handle subscription state changes via webhooks: customer.subscription.updated for plan changes, customer.subscription.deleted for cancellations.

Testing Stripe Integrations

Stripe provides test card numbers for every scenario: 4242 4242 4242 4242 for successful payments, 4000 0000 0000 9995 for declined payments, 4000 0025 0000 3155 for 3D Secure authentication. Use the Stripe CLI to forward webhooks to localhost during development: stripe listen --forward-to localhost:8000/webhooks/stripe/. The CLI also lets you trigger test webhook events: stripe trigger checkout.session.completed. Never use test mode API keys in production or production keys in development. Download our Stripe + Django integration template at proofmatcher.com.

Testing Payments Locally

Stripe's test mode lets you run the complete payment flow without moving real money. Use test API keys, which start with sk_test_ and pk_test_, and the published test card numbers: 4242 4242 4242 4242 succeeds, while other test numbers trigger declines, insufficient funds, or 3D Secure authentication, so you can check every path your code must handle. Any future expiry date and any three-digit CVC work.

Webhooks are harder to test because Stripe needs to reach your server. The Stripe CLI solves this: stripe listen --forward-to localhost:8000/webhooks/stripe/ forwards test events to your local server and prints the signing secret to use in development. You can also trigger specific events with stripe trigger payment_intent.succeeded to test your handlers without clicking through the checkout each time.

Verify Webhook Signatures Correctly

Anyone can send a POST request to your webhook URL, so never trust a webhook without verifying its signature. Stripe signs each event with your endpoint's signing secret, and the official libraries verify it with a single call such as stripe.webhooks.constructEvent(payload, signature, secret). The most common bug is passing a parsed JSON body instead of the raw request body: the signature is calculated over the exact bytes Stripe sent, so any framework middleware that parses or re-serialises the body will make verification fail. Configure your webhook route to receive the raw body, and make sure proxies in front of your server do not modify it.

Idempotency and Reliable Order Handling

Stripe may deliver the same webhook more than once, and events can arrive out of order. Make your handlers idempotent: store the IDs of processed events and skip duplicates, and update orders based on their current state rather than assuming a sequence. When your server creates objects through the API, send an Idempotency-Key header so a retried request after a network error does not create a second charge. Fulfil orders from the webhook, not from the success page redirect, because customers sometimes close the browser before the redirect completes.

Security and Compliance Essentials

  • Calculate amounts on the server. Never accept a price from the browser. Look up products and prices server-side, or use Stripe Price IDs, so users cannot change what they pay.
  • Keep card data away from your servers. Stripe Checkout and Stripe Elements collect card details in Stripe-hosted fields, which keeps your PCI compliance burden to the simplest level.
  • Protect your secret key. The secret key belongs only on the server, in environment variables. Use restricted keys with limited permissions for services that need only part of the API, and rotate keys if one is ever exposed.
  • Handle authentication requirements. Strong Customer Authentication in Europe means some payments require 3D Secure. Checkout handles this automatically; custom flows must handle the requires_action status.

Going Live Checklist

Before switching to live keys, complete your Stripe account activation and business details, register the production webhook endpoint and store its separate signing secret, and confirm which events it listens to. Configure your statement descriptor so customers recognise the charge on their bank statement, set up email receipts, and decide how you will handle taxes, whether with Stripe Tax or your own logic. Test a real low-value payment and a refund in live mode, then monitor the Stripe Dashboard and your webhook logs closely during the first days. Finally, set up alerts for failed webhooks and disputes so problems reach you before they reach your customers.

Handling the Subscription Lifecycle

Subscriptions keep changing after the first payment, and your application must stay in sync. Listen for customer.subscription.created, customer.subscription.updated, and customer.subscription.deleted to update the user's plan and access. Handle invoice.payment_failed by notifying the customer and pointing them to the Customer Portal to update their card; Stripe's automatic retry settings, known as Smart Retries, will attempt the payment again over the following days. Decide in advance what happens during that period, for example keeping access for a grace period before downgrading. When customers change plans mid-cycle, Stripe calculates prorations automatically, so show the resulting amount before they confirm.

Refunds and Disputes

Issue refunds through the API or Dashboard, and update the order status when you receive the charge.refunded event. Disputes, also called chargebacks, arrive as charge.dispute.created events with a deadline for submitting evidence. Keep records that help you respond: order details, delivery or download logs, customer communication, and your terms of service. Clear product descriptions, a recognisable statement descriptor, and responsive support prevent most disputes before they start.

Keep Your Own Records in Sync

Stripe is the source of truth for payments, but your application needs its own records for access control and reporting. Store the Stripe customer ID on the user, and the subscription ID, status, current period end, and price on your subscription table, updated from webhooks. Never grant access based only on a redirect back from Checkout. Periodically reconcile your records against Stripe, for example with a nightly job that lists active subscriptions, so a missed webhook never leaves a paying customer locked out or a cancelled one with free access.