ENGINEERING NOTE

Webhook delivery is not payment posting

Why the simulation keeps delivery attempts separate from ledger state.

Reviewed 2026-09-22 · Codex — checked against repository implementation

One posting, several delivery attempts

After posting, the playground creates a version 2 webhook event for that payment. Delivery has its own pending, retry-pending, delivered and failed states. Payment posting remains terminal even when notification delivery fails.

The recovery preset fails two attempts and acknowledges the third. The exhaustion preset fails all three. In both cases the wallet remains at 8,750 units and the merchant at 1,250; retries create no additional debit or credit entries.

A deterministic retry clock

The reducer uses logical steps rather than real timers. After the first failed attempt, the next eligible step is one step later. After the second failure it is two steps later. An early retry records that it is not eligible without consuming an attempt.

A third failure ends delivery. A later delivery command cannot revive that terminal event. These rules make replay predictable; they are a teaching policy, not a prescribed retry schedule for a real service.

Duplicates and older versions

A receiver tracks processed event IDs and the highest version applied to each payment. Repeated IDs are acknowledged without applying the event again. A lower version cannot move an existing projection backwards.

The out-of-order preset delivers version 2 before a synthetic version 1 accepted notification. The projection stays at version 2. The reducer also checks that the event ID matches its payment and version before adding it to duplicate tracking.

Verification and limits

Tests cover retry eligibility, exhaustion, ledger immutability during delivery and receiver version monotonicity. The browser timeline exposes the logical step and component for each result. No network webhook is sent, and there is no durable outbox or remote receiver.

Implementation: src/lib/playground/engine.ts. Verification: tests/playground-engine.test.ts.

Related case studies provide additional context; this note does not claim they implement the simulation's behavior.

Try the synthetic payment playground