Farpost · Payments
$ The Webhook Redelivery Stripe Warns You About
Stripe is explicit that it doesn't guarantee a webhook is delivered exactly once — the same event can arrive twice. Farpost's handler for a paid invoice was overwriting a job's paid-timestamp every single time that event arrived, including on a redelivery of an event it had already processed correctly.
That meant a redelivered, already-handled webhook could silently corrupt a record that was already correct — replacing an accurate paid-date with a later, wrong one, with nothing in the logs to suggest anything had gone wrong.
async def _handle_invoice_paid(invoice) -> None:
"""Mark platform_fee_collected on the job when the invoice is settled.
Stripe does not guarantee at-most-once webhook delivery — a redelivered
invoice.payment_succeeded was silently overwriting platform_fee_paid_at with a
later timestamp on every redelivery, corrupting the actual paid-date. Now a
no-op once already marked collected."""
invoice_id = invoice["id"]
job = await Job.find_one({"payment.platform_fee_invoice_id": invoice_id})
if not job:
return
if job.payment.platform_fee_collected:
return # already recorded — a redelivery, not a new event
job.payment.platform_fee_collected = True
job.payment.platform_fee_paid_at = datetime.now(timezone.utc)
job.payment.status = PaymentStatus.PAID
await job.save()The fix
The handler now checks whether the job is already marked platform_fee_collected before doing anything else, and returns immediately if so. A redelivery of an event that already succeeded becomes a no-op instead of a second write, so the paid-date it recorded the first time can't be overwritten by a later, incorrect one.
Why this matters
It's easy to build the happy path for a webhook — event arrives, update the record — and never consider what a second, redundant delivery of the exact same event does to that same update. Recognizing that a webhook handler needs to be idempotent, not just correct on a single delivery, is a specific and easy-to-skip form of defensive design.
