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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Always verify a webhook's signature against a shared secret before trusting its contents.
- 2Read the raw request body for signature checks — parsed JSON won't match the signed bytes.
- 3Return 200 quickly and delegate the actual work so the provider considers the event delivered.
Related explainers
python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI()
Building a WebSocket chat with FastAPI
websockets
broadcast
connection-management
Intermediate
9 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
python
import random from typing import Iterator, List
How reservoir sampling picks k items
reservoir-sampling
streaming
randomness
Intermediate
5 steps
python
import secrets from django.contrib.auth import authenticate, login from django.core.cache import cache
Two-factor login with OTP in Django
two-factor-auth
one-time-passwords
caching
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/handling-stripe-webhooks-in-fastapi-explained-python-c335/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.