80% off for waitlist membersJoin now and lock in Launch from $39.80 or Lifetime from $49.80 

← Back to Guides

Implementing Stripe Checkout in a Headless WooCommerce Store

WPBundle Team··12 min read
headless woocommerce stripe checkoutwoocommerce headless paymentsstripe woocommerce headlessheadless checkout stripe

Payment integration is where most headless WooCommerce projects stall. You've built a beautiful Next.js frontend, wired up the product catalogue via the REST API, and even managed cart sessions — but the moment a customer clicks "Pay", everything gets complicated. The checkout flow that WooCommerce hands you for free in a traditional theme simply doesn't exist in a headless setup. You have to rebuild it, and Stripe is how most teams do it.

This guide covers the three practical approaches to implementing Stripe checkout in a headless WooCommerce store: Stripe Checkout (hosted redirect), Stripe Elements (embedded card form), and the WooCommerce Stripe gateway via REST API. Each has trade-offs around control, PCI compliance, and development effort. We'll walk through when to use each, how webhook handling ties it all together, and the common mistakes that trip teams up.

TL;DR

Use Stripe Checkout (hosted redirect) for the fastest, most PCI-compliant integration — it takes hours, not weeks. Use Stripe Elements when you need full UI control over the payment form. Use the WooCommerce Stripe gateway via REST API only when you need WooCommerce to remain the payment processor (e.g. for plugin compatibility). All three approaches require webhook handling for reliable order confirmation.

Why payment is the hardest part of headless

In a traditional WooCommerce store, the checkout page is a server-rendered form that handles payment tokenisation, order creation, stock reduction, email triggers, and redirect — all in a single page load. When you go headless, you lose all of that. Your frontend is a static or server-rendered application with no direct access to WooCommerce's PHP internals.

This means you must handle three concerns that WooCommerce normally manages for you: securely collecting card details, creating an order in WooCommerce with the correct payment status, and confirming the payment was actually captured (not just initiated). Get any one of these wrong and you'll either leak sensitive data, create orphaned orders, or ship products that were never paid for.

62%

Of headless projects cite payments as the hardest integration

3

Viable Stripe integration approaches for headless Woo

SAQ-A

PCI level achievable with Stripe Checkout (simplest)

Approach 1: Stripe Checkout (hosted redirect)

Stripe Checkout is a Stripe-hosted payment page. Your frontend redirects the customer to Stripe's domain, they enter their card details on Stripe's servers, and Stripe redirects them back to your site with a session ID. You never touch card data — it never reaches your infrastructure at all.

This is the approach we recommend for most headless WooCommerce stores, particularly those migrating from a traditional setup. The integration is straightforward: your Next.js API route (or server action) creates a Stripe Checkout Session with the line items, then returns the session URL to the frontend. The frontend redirects. Stripe handles the rest.

The flow looks like this: customer clicks "Checkout" → your backend creates a Stripe Checkout Session with line items and prices → frontend redirects to Stripe → customer pays → Stripe redirects to your success URL → your webhook handler creates or updates the WooCommerce order.

Pros

  • Fully PCI-compliant out of the box (SAQ-A) — card data never touches your servers
  • Stripe handles 3D Secure, SCA, localisation, and mobile optimisation
  • Built-in support for Apple Pay, Google Pay, and 40+ payment methods
  • Can be implemented in a single afternoon
  • Stripe handles payment page performance and uptime

Cons

  • Customer leaves your site during checkout (redirect)
  • Limited control over the payment page UI and branding
  • Additional Stripe session overhead (small latency on redirect)
  • Cannot embed in a single-page checkout flow
  • Promotional codes require Stripe-side configuration

PCI compliance matters

If you collect, process, or store card numbers on your own servers, you take on significant PCI DSS obligations. Stripe Checkout eliminates this entirely because card data never reaches your infrastructure. For most small-to-medium WooCommerce stores, this alone makes it the right choice. Do not underestimate the cost and complexity of PCI compliance — it is not something you want to "figure out later".

Approach 2: Stripe Elements (embedded card form)

Stripe Elements gives you a set of pre-built, customisable UI components that render a secure card input directly on your page. The card details are collected inside a Stripe-hosted iframe, so sensitive data still never touches your servers, but the customer stays on your domain throughout the entire checkout flow.

With Elements, you create a PaymentIntent on your backend, pass the client secret to the frontend, and use Stripe.js to render the Payment Element. When the customer submits, Stripe.js confirms the PaymentIntent directly with Stripe's API. Your backend then listens for a webhook to confirm the payment succeeded and creates the WooCommerce order.

This approach is ideal when you've invested heavily in a custom checkout experience and redirecting customers to Stripe would break the flow. It's also the better option if you need a single-page checkout with shipping address, billing address, and payment all on one screen. The trade-off is more frontend code and a more complex integration.

Pros

  • Customer stays on your site — no redirect, seamless UX
  • Full control over checkout page layout and styling
  • Still PCI-compliant (SAQ A-EP) via Stripe-hosted iframes
  • Supports Apple Pay, Google Pay, and Link via the Payment Element
  • Can integrate with complex checkout flows (multi-step, one-page)

Cons

  • More frontend code to write and maintain
  • You handle 3D Secure confirmation flows yourself
  • Slightly higher PCI scope than Stripe Checkout (SAQ A-EP vs SAQ-A)
  • Must manage PaymentIntent lifecycle (creation, confirmation, error handling)
  • More testing required across browsers and devices

Approach 3: WooCommerce Stripe gateway via REST API

The third approach keeps WooCommerce as the payment processor. Instead of creating Stripe sessions or PaymentIntents directly, you use WooCommerce's REST API to create an order and process payment through the WooCommerce Stripe Payment Gateway plugin. The plugin handles the Stripe integration on the WordPress side.

In practice, you collect a payment method token on the frontend using Stripe.js, then send that token to WooCommerce's order endpoint along with the order details. WooCommerce's Stripe gateway charges the card using the token and updates the order status. This keeps all payment logic inside WooCommerce, which means existing plugins that hook into WooCommerce's payment flow (subscriptions, invoicing, accounting integrations) continue to work.

Pros

  • WooCommerce plugins that depend on payment hooks still work
  • Order and payment are created atomically in WooCommerce
  • Familiar to teams already running WooCommerce Stripe gateway
  • WooCommerce handles refunds, order notes, and email notifications
  • No separate webhook handler needed for order creation

Cons

  • Tightly couples your frontend to WooCommerce's payment flow
  • REST API latency adds to checkout time — see our guide on slow checkouts
  • WooCommerce Stripe gateway must be installed and configured
  • Token passing between frontend Stripe.js and WooCommerce API can be fragile
  • Harder to switch away from WooCommerce later

When to use this approach

Choose the WooCommerce gateway approach when you need WooCommerce Subscriptions, WooCommerce Memberships, or other plugins that hook into woocommerce_payment_complete. If you're building a simpler store without these dependencies, Stripe Checkout or Elements will give you a faster, cleaner integration. If your checkout is already slow, adding more REST API calls will make it worse.

Choosing the right approach

  • Stripe Checkout: best for MVPs, small teams, and stores that prioritise speed to market
  • Stripe Elements: best for custom checkout UX, single-page checkouts, and brand-conscious stores
  • WooCommerce gateway: best when WooCommerce plugins depend on the payment lifecycle
  • All three approaches support Apple Pay and Google Pay with configuration
  • All three approaches require HTTPS in production
  • Stripe Checkout is the only approach that qualifies for PCI SAQ-A (simplest tier)

For most teams building a headless WooCommerce store on a budget, we recommend starting with Stripe Checkout. You can always migrate to Elements later if you need more UI control. The webhook handling and order synchronisation patterns are the same regardless of which approach you choose, so the migration is manageable.

Webhook handling for order confirmation

Regardless of which Stripe integration you choose, webhooks are essential. Never rely solely on the client-side redirect or confirmation to mark orders as paid. Customers close browser tabs, lose connectivity, or navigate away before your success page loads. The webhook is the only reliable signal that payment was captured.

Set up a webhook endpoint in your Next.js application (e.g. /api/webhooks/stripe) that listens for checkout.session.completed (for Stripe Checkout) or payment_intent.succeeded (for Elements). When the event arrives, verify the webhook signature using your Stripe webhook secret, then create or update the corresponding WooCommerce order via the REST API.

Webhook security

Always verify Stripe webhook signatures. Without signature verification, anyone can send a fake webhook to your endpoint and mark orders as paid. Use stripe.webhooks.constructEvent() with your webhook signing secret. Never skip this step, even in staging environments — build the habit early.

Key events to listen for:

  • checkout.session.completed — payment succeeded via Stripe Checkout
  • payment_intent.succeeded — payment confirmed via Elements or direct API
  • payment_intent.payment_failed — payment declined or failed (update order status)
  • charge.refunded — handle refunds initiated from the Stripe dashboard
  • charge.dispute.created — alert on chargebacks for manual review
  • invoice.payment_succeeded — for Stripe Billing / subscription renewals

Testing with Stripe test mode

Stripe's test mode is one of the best developer experiences in payments. Use your test API keys (prefixed with sk_test_ and pk_test_) and Stripe's test card numbers to simulate every scenario: successful payments, declined cards, 3D Secure challenges, and network errors.

Set up a separate webhook endpoint for test mode using the Stripe CLI's stripe listen --forward-to command. This forwards test webhook events to your local development server without needing a public URL. For staging environments, create a separate webhook endpoint in the Stripe dashboard pointed at your staging domain.

4242...4242

Test card number for successful payments

4000...0002

Test card that always declines

4000...3220

Test card that triggers 3D Secure

Apple Pay and Google Pay considerations

Both Apple Pay and Google Pay work with all three Stripe integration approaches, but the setup differs. With Stripe Checkout, they're enabled automatically — Stripe detects the customer's device and shows the appropriate wallet button. With Stripe Elements, you use the Payment Request Button element or the newer Payment Element, which handles wallet detection for you.

For Apple Pay, you must verify your domain with Apple by hosting a verification file at /.well-known/apple-developer-merchantid-domain-association. This is straightforward in Next.js — place the file in your public/.well-known/ directory. Google Pay requires no domain verification for test mode but does require registration for production.

Wallet payments boost conversion

Stores that offer Apple Pay and Google Pay typically see a 5-10% uplift in mobile conversion rates. The friction reduction is significant — customers authenticate with Face ID or fingerprint instead of typing a 16-digit card number on a mobile keyboard. Make this a priority, not an afterthought.

Common errors and debugging

After helping teams implement headless WooCommerce Stripe checkout across dozens of stores, these are the issues we see most frequently:

1. Webhook signature verification failures

The most common cause is using the wrong webhook secret. Stripe generates a different signing secret for each webhook endpoint. If you're using stripe listen locally, it gives you a temporary secret (whsec_...) that differs from the one in your Stripe dashboard. Make sure your environment variables match the correct endpoint.

2. Duplicate orders

This happens when both your success page and your webhook handler create orders. Pick one source of truth — the webhook — and use the success page only to display a confirmation. Use Stripe's idempotency keys and your own order reference mapping to prevent duplicates even if the webhook fires twice.

3. Currency mismatches

Stripe expects amounts in the smallest currency unit (pence for GBP, cents for USD). If your WooCommerce product is £29.99, you must pass 2999 to Stripe, not 29.99. This catches out almost every team at least once. Always multiply by 100 for zero-decimal currencies and handle edge cases for currencies like JPY that have no decimal places.

  • CORS errors: ensure your Stripe API calls happen server-side, not in the browser
  • Missing metadata: always attach WooCommerce order ID or cart ID to the Stripe session
  • Payment element not rendering: check that Stripe.js is loaded before mounting Elements
  • Webhook timeouts: return a 200 response immediately, process the order asynchronously
  • 3D Secure failures: handle the requires_action PaymentIntent status in your frontend
  • Test vs live key mismatch: the most embarrassing and most common production issue

Security checklist

Before going live with any Stripe integration in a headless WooCommerce store, verify every item on this list. Payment security is not optional — a single breach can end a business. If you're following our WooCommerce launch checklist, add these items to it.

  • HTTPS enforced on all pages, not just checkout
  • Stripe API secret keys stored in environment variables, never in client-side code
  • Webhook signature verification implemented and tested
  • Content Security Policy headers configured to allow Stripe.js iframes
  • No card numbers logged, stored, or transmitted through your servers
  • Stripe Radar enabled for fraud detection (free with Stripe)
  • Test mode disabled and live keys deployed for production
  • Error messages do not expose internal system details to customers

Never log card data

It sounds obvious, but we've seen it in production codebases:console.log(req.body) on a payment endpoint that receives raw card data. Even if you're using Stripe Elements and think card data can't reach your server, do not log request bodies on payment-related endpoints. One misconfiguration and you're in PCI scope — and potentially in breach.

Putting it all together

The best headless WooCommerce Stripe checkout integration is the simplest one that meets your requirements. Start with Stripe Checkout if you want to launch quickly and stay out of PCI scope. Move to Stripe Elements when your checkout UX demands it. Fall back to the WooCommerce gateway only when plugin compatibility requires it.

Regardless of approach, invest time in webhook handling, test coverage, and error handling. Payment is the one part of your store that absolutely cannot fail silently. Use Stripe's test mode exhaustively, simulate every failure path, and monitor your webhook delivery success rate in the Stripe dashboard.

If you're planning a migration to headless WooCommerce, payment integration should be the first thing you prototype — not the last. It's the piece most likely to change your architecture decisions, and the piece where mistakes are most costly. Get it right early and the rest of the build will feel straightforward by comparison.

Ready to go headless?

Join the WPBundle waitlist and get beta access completely free.

Join the Waitlist