---
title: "Stripe Webhook"
method: POST
path: "/api/v1/commerce/webhook"
tags: ["commerce", "commerce"]
---

# Stripe Webhook

`POST /api/v1/commerce/webhook`

## Request

- Base URL: `https://app.goodleads.club` (the document declares no server; this is the origin it was published from)
- URL: `https://app.goodleads.club/api/v1/commerce/webhook`
- Auth: none declared

Stripe webhook handler — verifies signature, marks the order paid, acks.

Fulfillment (cohort → workbook → S3 → emails) runs AFTER the 200 is sent,
as a FastAPI BackgroundTask on the same worker (`_fulfill_paid_order`).
Stripe treats a slow response as a failure and redelivers, and this week's
live deliveries took 11.5–13.9 s — a fulfillment that runs inside the
request is one slow S3 write away from building the same file twice and
emailing the buyer twice (2026-09-05). The ack now carries only the
money-side writes: the order is 'paid' before Stripe hears 200.

Idempotent: a delivered order no-ops; a 'paid' order inside its
fulfillment lease (`FULFILLMENT_LEASE`) is the same build still running
and no-ops too; the pending→paid write is a conditional UPDATE so two
concurrent deliveries can't both claim it. Nothing the buyer sees changed:
the success page already polls `/orders/by-session` until 'delivered'.

## Response `200`

Successful Response

- unknown

---

[API](https://skmtc.dev/goodleads/apis/formation-gtm-api.md) · [All operations](https://skmtc.dev/goodleads/apis/formation-gtm-api/llms.txt) · [OpenAPI document](https://skmtc.dev/goodleads/apis/formation-gtm-api/revisions/2068d13a82cf?raw)
