Skip to content

Stripe webhook receiver (unauthenticated; signature is the auth)

POST
/api/billing/webhooks/stripe

Verify, dedup, project, acknowledge (§5.6).

No auth dependency by design. Nothing derived from the request is trusted before verify_and_parse_webhook returns, and nothing derived from it is ever logged — a rejected delivery is unauthenticated attacker-controlled input, so the failure path logs the provider and the verdict, never the body, the headers, or the SDK’s exception text.

That holds for the verified payload too, which is why neither failure handler below passes exc_info: a SQLAlchemy StatementError renders its bound parameters, and the ledger insert binds raw — the whole event. Log exception classes and the identifiers we already hold, never a rendered exception. (_rollback_quietly keeps its traceback: a rollback executes no statement, so its errors carry no bound parameters to leak.)

Responses

200

Successful Response

{"status": "processed"} / {"status": "duplicate"} (§5.6 steps 2, 4), or {"status": "unresolvable"} — an event too old to still be a checkout race, whose subscription this deployment will never learn (_UNMAPPABLE_EVENT_MAX_AGE; BUG-200).

Declared here rather than in app/schemas/billing.py on purpose: this module shares no request/response vocabulary with the authenticated billing routes, and the only consumer is a gateway that reads the status code, not the body.

object
status
required
string