Next.js template
Reliable Stripe webhooks, ready to run
A working Next.js example that verifies Stripe events, queues them through RocketStack, and processes them asynchronously in a Function.
Architecture
Source path: examples/nextjs-stripe-webhooks
What the template includes
Next.js webhook route
App Router handler at POST /api/webhooks/stripe.
Stripe signature verification
Uses the raw request body and Stripe’s official server library.
RocketStack Queue integration
Enqueues a concise payload with the Stripe event ID as idempotencyKey.
RocketStack processing Function
Handles common Stripe event types asynchronously after enqueue.
Environment-variable template
Server-side secrets only — no NEXT_PUBLIC_ API keys.
Local Stripe CLI testing guide
Forward events locally and verify Queue + Function delivery.
Architecture
Stripe | v Next.js webhook route | v RocketStack Queue | v RocketStack Function
Key code excerpts
Verify the signature
export async function POST(request: Request) {
const signature = request.headers.get('stripe-signature')
if (!signature) {
return Response.json(
{ error: 'Missing Stripe-Signature header' },
{ status: 400 }
)
}
let event
try {
const rawBody = await request.text()
event = constructStripeEvent(rawBody, signature)
} catch {
return Response.json(
{ error: 'Invalid Stripe signature' },
{ status: 400 }
)
}
try {
const payload = toQueuePayload(event)
await enqueueStripeEvent(payload)
return new Response(null, { status: 202 })
} catch (error) {
return Response.json(
{ error: 'Unable to enqueue event' },
{ status: 500 }
)
}
}Add the event to Queue
export async function enqueueStripeEvent(
payload: StripeQueuePayload
): Promise<{ messageId: string; status: string }> {
const queueId = requiredEnv('ROCKETSTACK_QUEUE_ID')
const queues = getQueuesClient()
const response = await queues.enqueueMessage(queueId, {
payload,
idempotencyKey: payload.eventId
})
const data = response.data?.data
if (!data?.messageId) {
throw new Error('Queue enqueue returned an unexpected response')
}
return {
messageId: data.messageId,
status: data.status
}
}Process the queued event
exports.handler = async function handler(event) {
const payload = event?.payload
if (!payload?.eventId || !payload?.eventType) {
throw new Error('Invalid queue delivery payload')
}
const key = processedKey(payload.eventId)
const processedStore = createKvProcessedEventStore()
if (processedStore && await processedStore.hasProcessed(payload.eventId)) {
return {
success: true,
eventId: payload.eventId,
eventType: payload.eventType,
processedAt: new Date().toISOString(),
skipped: true
}
}
await applyApplicationLogic(payload)
if (processedStore) {
await processedStore.markProcessed(payload.eventId)
}
return {
success: true,
eventId: payload.eventId,
eventType: payload.eventType,
processedAt: new Date().toISOString()
}
}Setup steps
Open the READMEStep 1
Clone the example
Copy examples/nextjs-stripe-webhooks and install dependencies.
Step 2
Add environment variables
Set STRIPE_WEBHOOK_SECRET, ROCKETSTACK_API_KEY, and ROCKETSTACK_QUEUE_ID.
Step 3
Configure Queue and Function
Deploy process-stripe-event and point the Queue delivery target at it.
Step 4
Forward a Stripe test webhook
Use stripe listen and stripe trigger to exercise the full path.
Full install, Stripe CLI, and troubleshooting steps are in examples/nextjs-stripe-webhooks/README.md.
How duplicate events are handled
Queue idempotencyKey
The webhook route sets idempotencyKey to the Stripe event ID so duplicate deliveries reuse the same queue message when accepted by Queue.
Optional KV marker
The Function includes a demo ProcessedEventStore backed by RocketStack KV. KV supports conditional writes via ifVersion, but not a simple create-if-absent claim.
Application database
For irreversible side effects, persist processed event IDs in your own database. This template does not claim exactly-once processing.
Production considerations
- Keep Stripe and RocketStack secrets server-side only
- Queue only the fields you need — avoid full payment payloads
- Make Function side effects safe to run more than once
- Monitor failed deliveries and dead-lettered messages in the console
- Point Stripe’s production webhook at your deployed route
- Review Free plan usage limits before going live
Related workflows
Run the template with RocketStack
Create a free account, get an API key, and connect the Queue and Function used by the example.
