Gazebo
    ServicesAgentsDocsSpecWritingPricing
    Log inSign up
    Log in
    GazeboWorkflowsStripeHow to Set Up Stripe Webhooks

    Workflow

    How to Set Up Stripe Webhooks

    Configure Stripe to send events to your server and verify them securely.

    Last updated June 2026

    That's 5 steps.

    Describe it once — Gazebo generates a plan, you approve it, it runs.

    "Set up Stripe webhooks for checkout.session.completed"

    Let Gazebo handle this

    What you're doing and why it's painful

    Stripe webhooks let your server respond to payment events — a subscription renewing, a checkout completing, a payment failing. Without them your app has no way to know what happened after a user left your checkout page.

    The pain: setting them up requires navigating three separate sections of the Stripe dashboard, generating a signing secret, adding it to your environment, wiring up signature verification in your code, and making your local endpoint publicly accessible for testing. Miss any step and webhooks silently fail.

    Prerequisites

    • A Stripe account (test or live)
    • A publicly accessible server URL (or a tunnel like Stripe CLI for local dev)
    • Your server running and able to accept POST requests

    Step 1 — Create a webhook endpoint in Stripe

    1. Log in to the Stripe Dashboard
    2. Go to Developers → Webhooks
    3. Click Add endpoint
    4. Enter your endpoint URL: https://yourdomain.com/api/webhooks/stripe
    5. Under Select events, choose the events you want to receive. For a basic checkout integration, add:
      • checkout.session.completed
      • payment_intent.succeeded
      • payment_intent.payment_failed
    6. Click Add endpoint

    Step 2 — Get the signing secret

    After creating the endpoint, Stripe shows you a Signing secret starting with whsec_. Copy it — you'll only see it once in this view (you can reveal it again later from the endpoint page).

    Step 3 — Add the signing secret to your environment

    Add the signing secret to your environment variables:

    STRIPE_WEBHOOK_SECRET=whsec_your_secret_here
    

    On Vercel: Project Settings → Environment Variables → Add. Set it for Production and Preview.

    On local: add it to your .env file (never commit this to git).

    Step 4 — Handle the webhook in your server

    Your endpoint must return a 200 response quickly, then process the event asynchronously. Always verify the signature using the signing secret before processing anything.

    import Stripe from 'stripe';
    import express from 'express';
    
    const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
    const app = express();
    
    // Important: use raw body for signature verification
    app.post(
      '/api/webhooks/stripe',
      express.raw({ type: 'application/json' }),
      (req, res) => {
        const sig = req.headers['stripe-signature']!;
    
        let event: Stripe.Event;
        try {
          event = stripe.webhooks.constructEvent(
            req.body,
            sig,
            process.env.STRIPE_WEBHOOK_SECRET!
          );
        } catch (err) {
          console.error('Webhook signature verification failed:', err);
          return res.status(400).send(`Webhook Error: ${err}`);
        }
    
        switch (event.type) {
          case 'checkout.session.completed':
            const session = event.data.object as Stripe.Checkout.Session;
            // Fulfil the purchase, provision access, send confirmation email, etc.
            console.log('Payment successful for session:', session.id);
            break;
          case 'payment_intent.payment_failed':
            // Handle failed payment — notify user, retry, etc.
            break;
        }
    
        res.json({ received: true });
      }
    );
    

    Critical: the route must use express.raw() not express.json(). If you parse the body as JSON before signature verification, Stripe's SDK can't verify it and every webhook will fail.

    Step 5 — Test locally with the Stripe CLI

    Install the Stripe CLI and forward events to your local server:

    stripe listen --forward-to localhost:3000/api/webhooks/stripe
    

    The CLI outputs a new local signing secret (different from your production one). Use it for local testing:

    STRIPE_WEBHOOK_SECRET=whsec_local_secret_from_cli
    

    Trigger a test event:

    stripe trigger checkout.session.completed
    

    Common errors and gotchas

    No signatures found matching the expected signature for payload You're using express.json() instead of express.raw(). The body is being parsed before Stripe can verify it. Fix: switch the route middleware to express.raw({ type: 'application/json' }).

    Webhook signing secret mismatch Your production and local secrets are different — this is correct and expected. Make sure you're using the Stripe CLI's local secret for dev, and the dashboard secret for production. Don't mix them.

    Events not arriving Check that your endpoint URL is publicly accessible. If you're running locally, you need the Stripe CLI tunnel — Stripe cannot reach localhost directly.

    Duplicate event processing Stripe may send the same event more than once (it retries on failure). Make your handler idempotent — check if you've already processed a given event.id before acting on it.

    Skip the manual steps.

    Describe it once — Gazebo generates a plan, you approve it, it runs.

    "Set up Stripe webhooks for checkout.session.completed"

    Let Gazebo handle this
    ← All Stripe workflowsAll workflows
    Gazebo

    IAM for AI agents. Scoped credentials, access policies, and audit trails — without rotating keys.

    Product

    • Pricing
    • Status

    Explore

    • Services
    • Agents
    • Workflows
    • Integrations

    Content

    • Writing
    • Topics
    • Blog
    • Docs

    Free Tools

    • Scanner

    Company

    • About
    • [email protected]
    • [email protected]

    © 2026 Gazebo. All rights reserved.

    PrivacyTermsSecurity