python 42 lines · 7 steps

Handling Stripe webhooks in FastAPI

A FastAPI endpoint that verifies Stripe's signature, then routes each event type to the right billing action.

Explained by highlit
1import stripe
2from fastapi import APIRouter, Request, Header, HTTPException
3 
4from app.config import settings
5from app.services.billing import (
6 activate_subscription,
7 mark_payment_failed,
8 cancel_subscription,
9)
10 
11router = APIRouter(prefix="/webhooks", tags=["webhooks"])
12 
13 
14@router.post("/stripe", status_code=200)
15async def handle_stripe_webhook(
16 request: Request,
17 stripe_signature: str = Header(..., alias="Stripe-Signature"),
18):
19 payload = await request.body()
20 
21 try:
22 event = stripe.Webhook.construct_event(
23 payload=payload,
24 sig_header=stripe_signature,
25 secret=settings.STRIPE_WEBHOOK_SECRET,
26 )
27 except ValueError:
28 raise HTTPException(status_code=400, detail="Invalid payload")
29 except stripe.error.SignatureVerificationError:
30 raise HTTPException(status_code=400, detail="Invalid signature")
31 
32 obj = event["data"]["object"]
33 
34 match event["type"]:
35 case "checkout.session.completed":
36 await activate_subscription(obj["customer"], obj["subscription"])
37 case "invoice.payment_failed":
38 await mark_payment_failed(obj["customer"], obj["id"])
39 case "customer.subscription.deleted":
40 await cancel_subscription(obj["customer"])
41 
42 return {"received": True}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Always verify a webhook's signature against a shared secret before trusting its contents.
  2. 2Read the raw request body for signature checks — parsed JSON won't match the signed bytes.
  3. 3Return 200 quickly and delegate the actual work so the provider considers the event delivered.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Handling Stripe webhooks in FastAPI — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code