Skip to main content
Accept credit card payments, subscriptions, and one-time charges by wiring Stripe into your Hiveku project.
Before you start: create a Stripe account and grab your test keys from the Stripe Dashboard > Developers > API keys. You’ll also need a product and price ID for whatever you’re selling.

Three Paths

Ask the project AI to set it up:
Add Stripe Checkout for a $29/month subscription. Use my Stripe 
test keys from env vars STRIPE_SECRET_KEY and 
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY. Wire up a /api/checkout route 
and a Buy button on the pricing page.
The AI adds env var references, installs the SDK, creates the API route, and updates the pricing page. Review before deploy.

Step 1: Add Env Vars

You need two keys, and you’ll add a third once webhooks are set up.
1

Get your keys from Stripe

Stripe Dashboard > Developers > API keys. Start with test mode:
  • sk_test_... — secret key (server-side only)
  • pk_test_... — publishable key (client-side OK)
2

Add them as env vars in Hiveku

See Configure Environment Variables. Use these names:
  • STRIPE_SECRET_KEY — server-side only
  • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY — client-side OK
Never expose STRIPE_SECRET_KEY in client-side code. It must stay in server-only routes. The NEXT_PUBLIC_ prefix on the publishable key is safe — that key is meant for the browser.

Step 2: Install the SDK

In your terminal:
npm install stripe @stripe/stripe-js

Step 3: Create a Checkout Session

Server-side route that creates a Stripe Checkout session:
// app/api/checkout/route.ts (Next.js App Router)
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(req: Request) {
  const origin = req.headers.get('origin') || 'https://yoursite.com';

  const session = await stripe.checkout.sessions.create({
    mode: 'subscription', // or 'payment' for one-time
    line_items: [
      { price: 'price_xxxxxxxxxxxxxx', quantity: 1 },
    ],
    success_url: `${origin}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${origin}/cancel`,
  });

  return Response.json({ url: session.url });
}
Get your price_xxx ID from Stripe Dashboard > Products > click your product > Pricing.

Step 4: Redirect from the Buy Button

Client-side, when the user clicks “Buy”:
'use client';

export function BuyButton() {
  async function handleBuy() {
    const res = await fetch('/api/checkout', { method: 'POST' });
    const { url } = await res.json();
    window.location.href = url;
  }

  return <button onClick={handleBuy}>Subscribe</button>;
}
That’s enough to accept payments. To reliably know when a payment succeeds, you also need webhooks.

Step 5: Set Up a Stripe Webhook

1

Open Stripe Dashboard > Webhooks

In Stripe, go to Developers > Webhooks and click Add endpoint.
2

Enter your endpoint URL

Use the Hiveku URL of your webhook route:
https://yoursite.hiveku.com/api/stripe-webhook
Or your custom domain if configured.
3

Select events to listen for

Common choices:
  • checkout.session.completed — one-time purchases and subscription starts
  • payment_intent.succeeded — any successful payment
  • customer.subscription.updated — plan changes, cancellations
  • customer.subscription.deleted — subscription ended
4

Copy the signing secret

Stripe shows a signing secret starting with whsec_. Copy it and add as env var STRIPE_WEBHOOK_SECRET.

Step 6: Handle the Webhook

Server-side route that validates the signature and processes events:
// app/api/stripe-webhook/route.ts
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(req: Request) {
  const sig = req.headers.get('stripe-signature');
  if (!sig) return new Response('Missing signature', { status: 400 });

  // IMPORTANT: use raw body for signature verification
  const body = await req.text();

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    return new Response(`Webhook error: ${(err as Error).message}`, { status: 400 });
  }

  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object as Stripe.Checkout.Session;
      // grant access, send receipt, update database...
      break;
    }
    case 'customer.subscription.updated': {
      const sub = event.data.object as Stripe.Subscription;
      // update user's plan/status in your DB
      break;
    }
  }

  return Response.json({ received: true });
}
Verify against the raw request body (await req.text()), not the parsed JSON. Parsing changes whitespace and breaks the signature. This is the single most common Stripe webhook bug.
See the serverless guide for more on webhook patterns.

Test It End-to-End

1

Use Stripe test cards

Checkout accepts these in test mode:
  • 4242 4242 4242 4242 — successful payment
  • 4000 0000 0000 0002 — generic decline
  • 4000 0000 0000 9995 — insufficient funds
Any future expiry and any 3-digit CVC.
2

Complete a test checkout

Click your Buy button, enter the test card, submit.
3

Check Stripe Dashboard

Payments > Test data should show the successful payment. The webhook endpoint’s Recent attempts log shows your endpoint returning 200.
4

Check your database or app

Whatever your webhook handler updates (grant access, add credits, etc.) should have happened.

Go Live

When you’re ready to accept real payments:
  1. In Stripe Dashboard, toggle from Test mode to Live mode
  2. Copy the live sk_live_... and pk_live_... keys
  3. Update your Hiveku env vars (use different values per environment if you want staging on test, production on live)
  4. Re-create the webhook in live mode — live webhooks are separate from test webhooks and have a new signing secret
  5. Update STRIPE_WEBHOOK_SECRET to the live value
  6. Do a small real-money test (then refund yourself)

Troubleshooting

You’re almost certainly parsing the body before verifying. Use await req.text() and pass that string to constructEvent. Also make sure STRIPE_WEBHOOK_SECRET is set correctly and matches the mode (test vs live) you’re operating in.
Product IDs differ between test and live modes. A price created in test mode doesn’t exist in live mode — you need to recreate products/prices in live mode, or use Stripe’s Products API to copy them over.
success_url and cancel_url must be fully-qualified URLs (including https://). /success won’t work — use ${origin}/success or hardcode the full URL.
Webhooks don’t use CORS (Stripe calls server-to-server), but your /api/checkout fetch from the browser is subject to same-origin rules. If you’re calling from a different domain, configure CORS on your API route. If same-domain, check that the route path is correct and the deployment succeeded.
Check the deployment log for errors in the webhook handler. Common causes: typo in env var name, SDK version mismatch, an unhandled async throw. Also check that the event type you’re testing is in your switch — Stripe logs the event type it sent, and you can see it in the Webhooks dashboard.

What’s Next?

Environment Variables

Manage your Stripe keys per environment

Serverless Guide

More on API routes and webhook patterns