[{"data":1,"prerenderedAt":180},["ShallowReactive",2],{"blog-blueprints/stripe-webhooks":3},{"id":4,"title":5,"body":6,"category":160,"date":161,"dateModified":161,"description":162,"draft":163,"extension":164,"faq":165,"featured":163,"headerVariant":166,"image":165,"keywords":165,"meta":167,"navigation":168,"ogDescription":169,"ogTitle":165,"path":170,"readTime":171,"schemaOrg":172,"schemaType":173,"seo":174,"sitemap":175,"stem":176,"tags":177,"twitterCard":178,"__hash__":179},"blog/blog/blueprints/stripe-webhooks.md","Stripe Webhooks Security Guide",{"type":7,"value":8,"toc":149},"minimark",[9,20,24,30,35,50,54,63,67,76,85,89,94,97,100,103,106,109,112,123,137],[10,11,12],"blueprint-summary",{},[13,14,15,19],"p",{},[16,17,18],"strong",{},"To secure Stripe webhooks,"," you need to: (1) verify webhook signatures using the Stripe signing secret, (2) implement idempotency to handle duplicate events safely, (3) respond quickly with 200 status before processing, (4) use the raw request body for signature verification, and (5) store the webhook endpoint secret securely. This blueprint covers webhook security patterns applicable to any backend.",[21,22],"blueprint-meta",{"time":23},"1-2 hours",[25,26,27],"tldr",{},[13,28,29],{},"Stripe webhooks must be verified with signature checking-anyone can POST to your webhook URL. Use the raw request body for verification, handle events idempotently, and always respond quickly (within 30 seconds). Never trust client-side payment confirmations.",[31,32,34],"h2",{"id":33},"signature-verification-nodejs-stripe","Signature Verification (Node.js) Stripe",[36,37,39],"code-block",{"label":38},"app/api/webhooks/stripe/route.ts",[40,41,46],"pre",{"className":42,"code":44,"language":45},[43],"language-text","import Stripe from 'stripe'\nimport { headers } from 'next/headers'\n\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)\nconst webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!\n\nexport async function POST(req: Request) {\n  const body = await req.text()  // Raw body required\n  const signature = headers().get('stripe-signature')!\n\n  let event: Stripe.Event\n\n  try {\n    event = stripe.webhooks.constructEvent(body, signature, webhookSecret)\n  } catch (err) {\n    console.error('Webhook signature verification failed:', err)\n    return new Response('Invalid signature', { status: 400 })\n  }\n\n  // Process the verified event\n  try {\n    await handleStripeEvent(event)\n  } catch (err) {\n    console.error('Error processing webhook:', err)\n    return new Response('Webhook handler failed', { status: 500 })\n  }\n\n  return new Response(JSON.stringify({ received: true }))\n}\n","text",[47,48,44],"code",{"__ignoreMap":49},"",[31,51,53],{"id":52},"idempotent-event-handling","Idempotent Event Handling",[36,55,57],{"label":56},"lib/stripe-handlers.ts",[40,58,61],{"className":59,"code":60,"language":45},[43],"async function handleStripeEvent(event: Stripe.Event) {\n  // Check if we've already processed this event\n  const existing = await db.webhookEvent.findUnique({\n    where: { stripeEventId: event.id },\n  })\n\n  if (existing) {\n    console.log(`Event ${event.id} already processed`)\n    return\n  }\n\n  // Record the event before processing\n  await db.webhookEvent.create({\n    data: {\n      stripeEventId: event.id,\n      type: event.type,\n      status: 'processing',\n    },\n  })\n\n  try {\n    switch (event.type) {\n      case 'checkout.session.completed':\n        await handleCheckoutComplete(event.data.object)\n        break\n      case 'customer.subscription.updated':\n        await handleSubscriptionUpdate(event.data.object)\n        break\n      case 'invoice.payment_failed':\n        await handlePaymentFailed(event.data.object)\n        break\n    }\n\n    await db.webhookEvent.update({\n      where: { stripeEventId: event.id },\n      data: { status: 'completed' },\n    })\n  } catch (err) {\n    await db.webhookEvent.update({\n      where: { stripeEventId: event.id },\n      data: { status: 'failed', error: err.message },\n    })\n    throw err\n  }\n}\n",[47,62,60],{"__ignoreMap":49},[31,64,66],{"id":65},"common-mistakes","Common Mistakes",[36,68,70],{"label":69},"❌ Wrong: Parsing JSON before verification",[40,71,74],{"className":72,"code":73,"language":45},[43],"// WRONG - This breaks signature verification\nconst body = await req.json()  // Don't do this!\nstripe.webhooks.constructEvent(JSON.stringify(body), sig, secret)\n\n// CORRECT - Use raw body\nconst body = await req.text()\nstripe.webhooks.constructEvent(body, sig, secret)\n",[47,75,73],{"__ignoreMap":49},[77,78,79],"warning-box",{},[13,80,81,84],{},[16,82,83],{},"Use raw body for signature verification."," Parsing the body as JSON then re-stringifying it changes the format, causing signature verification to fail. Always use the raw request body.",[31,86,88],{"id":87},"security-checklist","Security Checklist",[90,91,93],"h4",{"id":92},"pre-launch-checklist","Pre-Launch Checklist",[13,95,96],{},"Webhook signature verified",[13,98,99],{},"Raw body used (not parsed JSON)",[13,101,102],{},"Events handled idempotently",[13,104,105],{},"Response time under 30 seconds",[13,107,108],{},"Different secrets for test/live modes",[13,110,111],{},"Webhook endpoint uses HTTPS",[113,114,115,120],"stack-comparison",{},[116,117,119],"h3",{"id":118},"related-integration-stacks","Related Integration Stacks",[13,121,122],{},"Supabase + Stripe Integration\nFirebase + Stripe Integration\nNextAuth + Prisma Sessions",[124,125,126,132],"related-articles",{},[127,128],"related-card",{"description":129,"href":130,"title":131},"Full integration","/blog/blueprints/supabase-stripe","Supabase + Stripe",[127,133],{"description":134,"href":135,"title":136},"Cloud Functions","/blog/blueprints/firebase-stripe","Firebase + Stripe",[138,139,142,146],"cta-box",{"href":140,"label":141},"/","Start Free Scan",[31,143,145],{"id":144},"check-your-webhook-security","Check Your Webhook Security",[13,147,148],{},"Scan for webhook vulnerabilities.",{"title":49,"searchDepth":150,"depth":150,"links":151},2,[152,153,154,155,159],{"id":33,"depth":150,"text":34},{"id":52,"depth":150,"text":53},{"id":65,"depth":150,"text":66},{"id":87,"depth":150,"text":88,"children":156},[157],{"id":118,"depth":158,"text":119},3,{"id":144,"depth":150,"text":145},"blueprints","2026-02-11","Comprehensive security guide for Stripe webhook handling. Verify signatures, handle events idempotently, secure endpoint configuration, and prevent replay attacks.",false,"md",null,"purple",{},true,"Secure Stripe webhook implementation patterns.","/blog/blueprints/stripe-webhooks","12 min read","[object Object]","Article",{"title":5,"description":162},{"loc":170},"blog/blueprints/stripe-webhooks",[],"summary_large_image","O3H9-jIyvAAkgRhRGxIdhltZWI7KrYmKyw-AI5rPA0g",1775843932062]