Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

stripe-fastapi-webhooks

A small, production-minded FastAPI service that takes payments with Stripe Checkout and keeps orders correct with a verified, idempotent webhook. It shows the three things that break most Stripe integrations:

  1. Trusting the success page instead of the webhook. Orders change state only from signed Stripe events, never from the redirect.
  2. Unverified or replayed webhooks. Signatures are checked with HMAC-SHA256 (constant-time compare), secret rotation is supported, and events older than 5 minutes are rejected.
  3. Duplicate deliveries and out-of-order events. Each event id is stored in the same database transaction as its effect, so retries do nothing, and a late "failed" event can never overwrite a "paid" order.

No Stripe SDK: the signature scheme is implemented directly from Stripe's documentation, so the logic is readable and testable.

Measured result

$ python -m pytest -q
................
16 passed in 0.44s

Verified on Python 3.12 / FastAPI 0.141 on Windows 10. The tests use an in-memory database and a fake Stripe API (httpx.MockTransport), so they need no keys and no network. They cover: order creation and the exact parameters sent to Stripe (including an idempotency key), paid / unpaid / async-failed / refunded transitions, duplicate events, bad signatures, replayed timestamps, secret rotation, tampered payloads and malformed headers.

Endpoints

Method Path What it does
POST /checkout Creates a pending order and a Stripe Checkout Session; returns the payment URL
GET /orders/{id} Order status: pending, paid, failed or refunded
POST /webhooks/stripe Verifies the signature and applies the event once

Handled events: checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, charge.refunded. Anything else is acknowledged with 200 so Stripe stops retrying.

Run it with Stripe test mode

python -m venv .venv && .venv\Scripts\activate      # or source .venv/bin/activate
pip install -r requirements.txt
copy .env.example .env                               # fill in TEST keys only
uvicorn app.main:app --reload
stripe listen --forward-to localhost:8000/webhooks/stripe   # Stripe CLI, prints the whsec_ secret

Then POST /checkout with {"amount_cents": 4999, "product_name": "Test"}, open the returned URL and pay with Stripe's test card 4242 4242 4242 4242.

Design notes

  • Secret keys stay server-side; nothing sensitive reaches the browser.
  • Idempotency-Key on session creation prevents double sessions if a request is retried.
  • SQLite keeps the example self-contained; the same pattern works with Postgres (INSERT ... ON CONFLICT DO NOTHING).

Author

Marvin Palencia, founder of DeMark Studio, Miramichi, New Brunswick, Canada. Available for Stripe integrations, Python automation and FastAPI back-ends. Portfolio: marvin.demarkstudio.ca

MIT License.

About

Stripe Checkout + verified, idempotent webhook with FastAPI. Signature check without the SDK, replay protection, safe state transitions. 16 tests.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages