Build a custom storefront in Next.js 16 with Shoppex as the commerce engine — products, checkout, and webhook fulfillment in under 15 minutes.
Build a custom storefront on Next.js 16 (App Router) that:
Lists products from your Shoppex catalog on the server.
Starts a checkout session from a Server Action.
Redirects the customer to Shoppex hosted checkout.
Receives a signed webhook when the order is paid and runs your own fulfillment.
Time budget: 15 minutes. You need a Shoppex shop and an API key (shx_*).
If you do not have an API key yet, open Dashboard → Settings → Developer API, click Generate New API Key, pick scopes products.read payments.write webhooks.read, and copy the key (it is shown once).
A product page with a Server Action that creates a payment and redirects to hosted checkout. The API key stays on the server; the browser only ever sees the resulting checkout_url.app/products/[id]/page.tsx:
return_url is where Shoppex sends the customer after a successful payment. cancel_url is where they go if they abandon checkout.If you prefer the Stripe-style alias, POST /dev/v1/checkout/sessions is available too. The published JS SDK does not wrap that route yet, so the quickstart uses POST /dev/v1/payments, which already has first-class SDK support.
Create a webhook endpoint in the Shoppex dashboard (Settings → Webhooks → Add Endpoint) pointing at https://your-site.com/api/webhooks/shoppex, subscribe to order:paid, and copy the signing secret into SHOPPEX_WEBHOOK_SECRET.app/api/webhooks/shoppex/route.ts:
import { createHmac, timingSafeEqual } from 'node:crypto';import { NextRequest, NextResponse } from 'next/server';export async function POST(request: NextRequest) { const signature = request.headers.get('x-shoppex-signature'); if (!signature) { return NextResponse.json({ error: 'missing signature' }, { status: 401 }); } const raw = await request.text(); const expected = createHmac('sha512', process.env.SHOPPEX_WEBHOOK_SECRET!) .update(raw) .digest('hex'); const sigBuf = Buffer.from(signature, 'hex'); const expBuf = Buffer.from(expected, 'hex'); if (sigBuf.length !== expBuf.length || !timingSafeEqual(sigBuf, expBuf)) { return NextResponse.json({ error: 'invalid signature' }, { status: 401 }); } const event = JSON.parse(raw) as { event: string; data: { uniqid: string; status: string; customer_email: string; total: number }; created_at: number; }; if (event.event === 'order:paid') { // Your fulfillment: grant access, send license, provision subscription, etc. console.log(`Order ${event.data.uniqid} paid for ${event.data.customer_email}`); } return NextResponse.json({ received: true });}
Always verify the signature with constant-time comparison. The timingSafeEqual call above is what prevents timing-side-channel attacks. Never compare signatures with ===.
Return 2xx within a few seconds. Shoppex retries failed deliveries with backoff — do your real fulfillment work in a background job and ack the webhook immediately if fulfillment takes longer than a couple of seconds.
Run the app and point a tunnel at it so the webhook is reachable:
npm run dev# in a second terminal:ngrok http 3000
Set the webhook URL in the Shoppex dashboard to the ngrok URL + /api/webhooks/shoppex. In the dashboard, click Send Test Event on your webhook to verify the signature check passes.
Server-side catalog read, no API key in the browser
app/products/[id]/page.tsx
Server Action creates a payment and redirects to hosted checkout
app/api/webhooks/shoppex/route.ts
Signed webhook receiver with constant-time HMAC verification
lib/shoppex.ts
server-only SDK wrapper
Your frontend, your routing, your brand. Shoppex handled the PSP selection, 3DS, the hosted checkout page, the payment confirmation, the event delivery, and will also handle refunds, disputes, and subscriptions when you add them.