Step 3 of 4
Set Up Payments
Connect Stripe so your store can accept card payments — and so that paid orders actually reach your admin panel.
You'll need a Stripe account and your deployed store URL from Step 2 · Estimated time: 5–10 min
This step is optional. Cash on delivery and bank transfer work without any payment provider, so you can skip ahead to Step 4 and come back when you're ready to take cards.
1. Get Your Stripe Keys
Start in Test mode — you want to place a fake order end to end before real money is involved. There is a toggle in the top-right of the Stripe dashboard.
- Go to Stripe Dashboard → API keys
- Confirm the toggle says Test mode
- Copy both keys using the copy icon:
| You'll see | Save it as |
|---|---|
Publishable key (pk_test_…) | NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
Secret key (sk_test_…) | STRIPE_SECRET_KEY |
Add both to .env.local, either by editing the file or by re-running npm run setup.
2. Register the Webhook
A webhook is how Stripe tells your store that a payment succeeded. Without it, customers are charged and no order is ever created. Get this right and everything else follows.
Recommended: let the script do it
This creates the endpoint, subscribes exactly the right events, and writes the signing secret into .env.local:
npm run stripe:setupIt's safe to run more than once — an existing endpoint with the same URL is reused rather than duplicated. It registers the webhook against your deployed store, which is why this step comes after Step 2 — Stripe cannot reach localhost. For local development use npm run stripe:listen instead.
Manual alternative
- Go to Stripe Dashboard → Webhooks
- Click Add destination
- Select exactly these four events — search for each by name:
| Event | What it does |
|---|---|
payment_intent.succeeded | Creates the order when a payment clears |
payment_intent.payment_failed | Records the failed attempt |
charge.refunded | Marks the order refunded |
charge.refund.updated | Tracks refund status changes |
- Click Continue, choose Webhook endpoint, then Continue again
- Set the Endpoint URL to your store address followed by
/api/stripe/webhook:https://yourstore.com/api/stripe/webhook - Click Create destination
- Copy the Signing secret (
whsec_…) and save it asSTRIPE_WEBHOOK_SECRET
Followed an older version of this guide? Earlier revisions listed subscription events — checkout.session.completed, customer.subscription.* and invoice.*. Those are not the events this store listens for, and a webhook configured that way will never record an order. Open your endpoint in Stripe, replace the event list with the four above, or just run npm run stripe:setup to correct it.
Custom domain not connected yet? Use the Vercel URL from Step 2, e.g. your-project.vercel.app/api/stripe/webhook. If you add a domain later, update the endpoint URL here to match.
3. Testing Locally
Stripe can't reach localhost, so forward events to your machine while developing:
# One-time: install the Stripe CLI
brew install stripe/stripe-cli/stripe
stripe login
# Then, in a second terminal alongside npm run dev
npm run stripe:listenThe command prints its own whsec_… — use that one in .env.local while testing locally. It is different from your deployed endpoint's secret; each endpoint has its own.
4. Going Live
When test orders work end to end, switch over:
- Flip the Stripe dashboard to Live mode
- Copy the live keys (
pk_live_…andsk_live_…) into your hosting environment variables - Register the webhook again in live mode — test and live endpoints are separate, each with its own signing secret
- Redeploy, then run
npm run post-deployto confirm the live keys are in place
Checklist
- ☐Publishable and secret keys saved (test mode first)
- ☐Webhook endpoint created — via npm run stripe:setup or manually
- ☐Exactly the four events above are subscribed
- ☐Signing secret saved as STRIPE_WEBHOOK_SECRET
- ☐A test payment created a real order in the admin panel
Was this step clear?