All articles
PythonStripeFastAPI

Stripe Webhooks in Python: Signature Verification and Reliable Fulfillment

PyForge Team 7 min read2026-10-14

A customer paying is not the same as an order fulfilled. Browsers close, connections drop, and the success redirect your customer sees is a cosmetic detail — not proof of payment. The only signal you can trust is the webhook Stripe sends to your server.

Never trust the redirect

Treat success_url as decoration. The source of truth is the checkout.session.completed event, delivered server-to-server and signed. Build your fulfillment around that event and the user experience degrades gracefully instead of corrupting your orders.

python
@app.post("/webhooks/stripe")
async def stripe_webhook(request: Request):
    payload = await request.body()
    sig = request.headers.get("stripe-signature", "")
    event = stripe.Webhook.construct_event(
        payload, sig, WEBHOOK_SECRET
    )
    if event["type"] == "checkout.session.completed":
        fulfill(event["data"]["object"])
    return {"received": True}

Verify the signature, always

Stripe signs every webhook with HMAC-SHA256 and a timestamp. Verifying the signature proves the payload came from Stripe, and the timestamp window (five minutes by default) blocks replay attacks. Skipping this check means anyone who finds your endpoint can mark orders as paid.

Idempotent fulfillment

Stripe retries webhooks, so your handler will see the same event more than once. Store the event id and skip work you have already done — send the delivery email once, increment the sales counter once, license the key once.

python
async def fulfill(session):
    if await db.events.find_one({"_id": session["id"]}):
        return  # already processed
    await db.events.insert_one({"_id": session["id"]})
    await send_delivery_email(session["customer_email"])

Get these three details right — server-side events, signature verification, idempotency — and your store survives the messy reality of production traffic. We sell exactly this wiring as a ready-made asset because every indie seller re-derives it the hard way.