Skip to main content

max / makenotwork

Document the deliberate webhook-200/self-owned-retry trade (Payments) Risk remediation (fuzz 2026-07-06, Payments NOTE). No behavior change: record that ACKing a failed money event with 200 hands retry to the in-house queue + scheduler, so scheduler liveness is the only backstop for a failed webhook, and that the queue-insert-failed path returns 503 so nothing is silently dropped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-06 22:27 UTC
Commit: dae1711922d08d4b358ba0b2384cabacfb2b96b2
Parent: f661b5f
1 file changed, +10 insertions, -0 deletions
@@ -88,6 +88,16 @@ pub(super) async fn webhook_v2(
88 88 // scheduler), matching v1's failure path rather than relying solely on
89 89 // Stripe's redelivery window. Not marked processed, so a Stripe
90 90 // redelivery also still re-runs the idempotent handler.
91 + //
92 + // Deliberate trade (fuzz 2026-07-06, Payments): returning 200 below ACKs
93 + // the event to Stripe, so Stripe will not redeliver it on its own 3-day
94 + // schedule — the in-house queue + scheduler owns retry from here (richer:
95 + // local backoff + dead-lettering). The operational consequence is that
96 + // for a persistently-failing money event, **scheduler liveness is the
97 + // only retry backstop**; if the scheduler is down, a failed webhook has no
98 + // external redelivery. The `insert_failed_event` guard below returns 503
99 + // (inviting Stripe redelivery) if even the queue insert fails, so the net
100 + // is never "silently dropped".
91 101 tracing::warn!(event_id = %thin.id, error = ?e, "v2 event processing failed; queueing for retry");
92 102 if let Err(queue_err) = db::webhook_events::insert_failed_event(
93 103 &state.db,