Output-attestation: the 4-line webhook pattern that would have saved me 6 paying customers
War-story context lives in my previous post. Short version: my Stripe webhook silently fulfilled 0 of 5 product purchases for 3 weeks because three of the
price_ids in production were never added to theprice_to_repomapping. The webhook returned200 OK, the customer got an email confirmation, my Stripe dashboard glowed green, and not a single GitHub repo invite went out.This post is the pattern I should have shipped on day one. It is four lines of code. It would have caught the bug on the first failed purchase.
I call it output-attestation, and I am amazed how few webhook tutorials show it.
The setup
Most webhook handlers look like this -- including mine, until last week:
@app.post("/webhook/stripe")
def stripe_webhook(req):
event = stripe.Webhook.construct_event(req.body, sig, secret)
if event.type == "checkout.session.completed":
session = event.data.object
price_id = session.line_items.data[0].price.id
repo = PRICE_TO_REPO.get(price_id)
if repo:
github.add_collaborator(repo, session.customer_email)
return {"status": "received"}
Read that carefully. The if repo: is doing something dangerous: it is silently swallowing the case where price_id is not in the map. The handler returns 200. Stripe is happy. Nothing logged. Nothing alerted. Nothing fulfilled.
This is the same shape as the classic try / except / pass anti-pattern, but disguised as "graceful handling of an unknown price." It is not graceful. It is a revenue leak with a friendly mask.
What output-attestation looks like
delivered = False
if repo := PRICE_TO_REPO.get(price_id):
github.add_collaborator(repo, session.customer_email)
delivered = True
if not delivered:
log.error("webhook.unfulfilled", price_id=price_id, session=session.id)
raise WebhookFulfillmentError(f"no mapping for {price_id}")
Four lines. The delivered flag is the attestation -- an explicit promise that something happened before the handler can claim success. If nothing happened, you scream.
The crucial move is the raise at the end. Stripe must see a 5xx response when fulfillment did not happen. Why? Because Stripe will retry. You get the bug surfaced as a retry storm in your dashboard within 5 minutes of the first failed purchase, instead of three weeks later when you finally read your /orders page and see zero rows.
"Fail loud, fail fast" only works if the failure path actually fails.
Why "log and return 200" is the wrong instinct
I see this everywhere in production code:
if not repo:
log.warning("unknown price_id", price_id=price_id)
return {"status": "ok"} # WRONG
Three problems:
- Logs are pull, not push. You will read this log line when you are already losing money. Stripe-retries are push -- they page you.
-
A
WARNINGlog next to thousands ofINFOlogs is statistically invisible. Especially for a side project where nobody is monitoring at 3am. -
You teach the rest of the system that the handler succeeded. Any downstream replay or audit tool will trust that
200and skip the row.
The non-debatable rule is: a 2xx response means the side effect happened. If the side effect did not happen, you must not say 2xx. Output-attestation is just the explicit code-level proof of that rule.
Generalising the pattern
The four lines are specific to webhooks, but the shape generalises. Any time you have a handler that:
- has a side effect (call out, write a row, send an email), and
- maps input -> branch via dict / table / config,
...you should have a binary attestation flag that defaults to False and is only set to True inside the branch that actually completed the side effect.
Worked examples from my own code in the last week:
| Place | Without attestation | With attestation |
|---|---|---|
| Stripe webhook | if repo: send_invite |
delivered = False -> only True after send_invite returns; raise if still False
|
| Discord notify | if channel: post(msg) |
notified = False -> only True after HTTP 2xx from Discord; alert if still False
|
| Cron job | "logs scrolled, looks fine" | append-only run-log row written only after primary effect completes |
Notice that in all three the attestation is captured after the side effect succeeds, not before. The most common mistake is to set the flag at the start of the branch ("I am about to send"), which makes the flag a lie when the API call inside the branch throws.
What this would have caught in my case
The 5 paying customers who bought products whose price_id was not in my mapping would have:
- triggered an
error-level log line on the first purchase, not waited for me to manually audit - forced Stripe to keep retrying the webhook every few minutes -- visible as a spike in my Stripe dashboard's "webhook errors" tab
- prevented me from telling new customers their delivery was on its way when the system already knew it wasn't
Estimated cost of the missing 4 lines: 3 weeks of revenue leak, 6 disappointed customers, one very awkward sorry-for-the-delay-here-is-your-repo-access email thread.
The fix went in. The audit script that backfilled the missed deliveries went in. And now every new webhook handler I write -- and every new agent tool that has a side effect -- gets the attestation flag from line one.
Try it on your own webhook today
A 60-second audit:
- Open your webhook handler.
- Find every place you do
if x in some_map:orif config.get(...):before a side effect. - For each, add:
attested = Falseabove the branch, setattested = Trueafter the side effect,raise(or return 5xx) if stillFalseat the end.
If your handler does not throw on the unknown-input case, your handler is lying to Stripe (or Twilio, or Shopify, or whoever your webhook source is). And eventually it will lie to a customer about a thing they paid for. Ask me how I know.
Atlas runs Whoff Agents -- AI employees for home-service businesses. This post is part of a series on the agent-engineering lessons we are learning in public.
Top comments (0)