Receiving Stripe Webhooks: Endpoint Setup & Signature Verification

Updated October 6, 202511 min read

Stripe webhooks keep your backend in sync with payment events, subscription lifecycle updates, and billing changes. This guide covers endpoint implementation, dashboard registration, signature verification, testing flows, and the operational playbook Stripe recommends for high-volume webhook consumers.

Step 1: Build a Webhook Endpoint

Your endpoint can be written in any language as long as it accepts HTTPS POST requests and reads the raw request body for signature verification. Respond quickly with a 2xx status code before handing off heavy work to background jobs so Stripe does not time out delivery.

  • Expose a stable path such as /webhooks/stripe.
  • Parse JSON payloads cautiously—store the raw body for signature checks.
  • Keep latency below 10 seconds; Stripe will retry if the endpoint is slow or returns non-2xx responses.
# Ruby + Sinatra example
post '/webhook' do
  payload = request.body.read
  event = nil

  begin
    event = Stripe::Event.construct_from(JSON.parse(payload, symbolize_names: true))
  rescue JSON::ParserError
    status 400
    return
  end

  # TODO: handle the event by type
  status 200
end

Stripe maintains official server libraries for Ruby, Node.js, Python, Go, Java, and PHP; reuse them to reduce low-level parsing work.

Step 2: Register the Endpoint in Stripe

Add the endpoint via the Stripe Dashboard under Developers → Webhooks or create it programmatically through the API. Stripe requires a publicly accessible HTTPS URL in live mode.

  1. Click Create an event destination and paste your HTTPS endpoint URL.
  2. Select the event types you actually process—examples include payment_intent.succeeded, charge.failed, and customer.created.
  3. Copy the signing secret that Stripe generates. Store it as STRIPE_WEBHOOK_SECRET in your environment.

Step 3: Verify Stripe Signatures

Stripe signs each webhook with a shared secret and sends the signature in the Stripe-Signature header. Always validate the signature using the raw request body before trusting the payload, and reject events older than five minutes to prevent replay attacks.

# Ruby signature verification
endpoint_secret = ENV.fetch('STRIPE_WEBHOOK_SECRET')
payload = request.body.read
sig_header = request.env['HTTP_STRIPE_SIGNATURE']

begin
  event = Stripe::Webhook.construct_event(payload, sig_header, endpoint_secret)
rescue Stripe::SignatureVerificationError => e
  puts "Signature verification failed: #{e.message}"
  halt 400
end
// Node.js + Express example
import express from "express";
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_API_KEY!);
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET!;

app.post(
  "/webhooks/stripe",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.headers["stripe-signature"];

    try {
      const event = stripe.webhooks.constructEvent(
        req.body,
        signature as string,
        endpointSecret,
      );

      handleStripeEvent(event);
      res.status(200).send("OK");
    } catch (error) {
      console.error("Invalid Stripe signature", error);
      res.status(400).send("Webhook Error");
    }
  },
);

Signature checklist

  • Always use the raw body buffer; JSON middleware breaks signature validation.
  • Compare timestamps inside Stripe-Signature and reject events older than 300 seconds.
  • Rotate secrets when you regenerate them in the Dashboard and deploy code updates simultaneously.

Step 4: Test with Stripe CLI & Hooklistener

Stripe offers multiple testing tools. Use them alongside Hooklistener to forward events into local environments, inspect payloads, and replay failures safely.

Stripe CLI

  • Run stripe listen --forward-to localhost:4242/webhooks/stripe to pipe events to your local server.
  • Trigger sample events with stripe trigger payment_intent.succeeded or stripe trigger invoice.paid.
  • Check delivery history in the Dashboard to debug failures.

Hooklistener Tunnels

  • Capture live Stripe events with Hooklistener and forward them to your staging or local environment.
  • Replay stored payloads to reproduce edge cases without waiting for Stripe to resend.
  • Share event history across engineering and support teams for faster incident response.

Step 5: Handle Events Idempotently

Stripe delivers events at least once and occasionally out of order. Always gate processing on event.type and record event IDs you already handled so retries do not double-charge customers or create duplicate records.

import type Stripe from "stripe";

function handleStripeEvent(event: Stripe.Event) {
  const alreadyProcessed = hasEventBeenHandled(event.id);
  if (alreadyProcessed) {
    return;
  }

  switch (event.type) {
    case "payment_intent.succeeded":
      fulfillSubscription(event.data.object);
      break;
    case "customer.created":
      syncCustomer(event.data.object);
      break;
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  markEventHandled(event.id);
}
  • Persist processed event IDs in a durable data store (database or cache with TTL).
  • Fetch canonical objects from Stripe’s API when accuracy matters (for example, re-fetch the payment intent by ID).
  • Queue heavy work (email, invoicing, ERP sync) so your webhook response remains fast.

Step 6: Operational Best Practices

Security & Compliance

  • Use HTTPS in live environments; Stripe enforces TLS for production endpoints.
  • Exclude the webhook route from CSRF middleware in Rails, Django, or Laravel.
  • Limit subscribed events to the minimal set your integration needs.

Reliability

  • Stripe retries failed events for up to three days; monitor delivery logs for repeated failures.
  • Expose health checks and alerting around webhook latency and error rates.
  • Roll webhook secrets periodically and update your infrastructure in lockstep.

Key Steps & Tooling Overview

StepActionNotes
Create EndpointImplement /webhooks/stripeAccept POST, store raw body, respond quickly
Register EndpointDashboard or APIUse HTTPS, subscribe only to required events
Verify EventsValidate Stripe-SignatureUse endpoint secret, enforce timestamp tolerance
Test LocallyStripe CLI + HooklistenerForward events, replay payloads, inspect logs
Handle EventsSwitch on event.typePersist IDs to guarantee idempotency

Build Faster with Hooklistener

Hooklistener captures Stripe webhooks in real time, forwards them to your development environments, and preserves an audit trail for replay. Spend less time wiring tunnels and more time shipping payment features.

Forward Stripe events to local or staging endpoints instantly
Replay payloads to debug signature and idempotency issues
Share event history across teams for coordinated incident response
Receive delivery alerts before Stripe disables failing endpoints

Further Reading