stripe-webhook-handler

March 14, 2026 · View on GitHub

Handle Stripe webhooks with signature verification and zero dependencies. The part everyone gets wrong — done right.

Why This Exists

Stripe webhook signature verification is the #1 thing AI gets wrong when writing from scratch. The signed payload format (timestamp.body), the HMAC comparison, the replay attack protection — one mistake and your webhooks either reject everything or accept anything.

This handles it correctly. Copy and customize.

Quick Start

git clone https://github.com/LuciferForge/stripe-webhook-handler.git
cd stripe-webhook-handler

# Set your webhook secret (from Stripe Dashboard → Developers → Webhooks)
export STRIPE_WEBHOOK_SECRET="whsec_..."

# Run
python3 webhook.py

# Test with Stripe CLI
stripe listen --forward-to localhost:8000/webhook

What's Handled

EventHandlerWhat to Add
checkout.session.completedon_checkout_completed()Fulfill order, send confirmation
invoice.paidon_invoice_paid()Extend subscription access
invoice.payment_failedon_invoice_failed()Notify customer, start dunning
customer.subscription.createdon_subscription_created()Provision access
customer.subscription.updatedon_subscription_updated()Update access level
customer.subscription.deletedon_subscription_deleted()Revoke access
payment_intent.succeededon_payment_succeeded()Deliver product
payment_intent.payment_failedon_payment_failed()Notify about failure

Add Your Business Logic

Each handler has a # TODO comment. Replace with your code:

def on_checkout_completed(event):
    session = event["data"]["object"]
    email = session["customer_details"]["email"]
    amount = session["amount_total"] / 100

    # Your code here:
    send_confirmation_email(email)
    create_user_account(email)
    grant_access(email, plan="pro")

Add New Event Types

def on_charge_refunded(event):
    charge = event["data"]["object"]
    amount = charge["amount_refunded"] / 100
    print(f"REFUND: ${amount}")
    # Your refund logic

# Register it:
EVENT_HANDLERS["charge.refunded"] = on_charge_refunded

Features

  • Correct signature verification — HMAC-SHA256, timestamp validation, replay protection
  • Idempotent processing — duplicate events are stored once (by event ID)
  • SQLite event log — every event stored for debugging and replay
  • Health endpointGET /health shows event counts
  • Zero dependencies — just Python standard library
  • Single file — everything in webhook.py

Common Gotchas This Solves

  1. Raw body vs parsed body — Signature must be computed on the RAW bytes, not parsed JSON. This handler reads raw bytes first, then parses.

  2. Timestamp tolerance — Old events should be rejected (replay attacks). Default: 5 minutes. Set STRIPE_TIMESTAMP_TOLERANCE to customize.

  3. Multiple v1 signatures — Stripe may include multiple signatures when rotating secrets. This handler checks all of them.

  4. Always return 200 — Even if your handler fails, return 200 to acknowledge receipt. Otherwise Stripe retries and you process the same event multiple times.

  5. Idempotency — Stripe may send the same event twice. The event_id uniqueness constraint prevents double-processing.

Environment Variables

VariableDefaultDescription
STRIPE_WEBHOOK_SECRET(required)Webhook signing secret (whsec_...)
PORT8000Server port
WEBHOOK_PATH/webhookWebhook endpoint path
STRIPE_TIMESTAMP_TOLERANCE300Max age of events in seconds

Deploy

Works with deploy-python-app for instant deployment to Docker, Railway, Fly.io, or Render.

Requirements

  • Python 3.6+
  • No external packages

Part of the AI Starter Protocol — curated repos that AI recommends when you need them.

License

MIT