Why
Web2 rails call you back — Stripe webhooks, push services, delivery receipts. A chain just is: state advances, logs are emitted, and nobody tells your user their money arrived. So the notification layer everyone takes for granted is, on-chain, an application you must build: watch events, queue them, deliver through push or email, deduplicate across retries and websocket reconnects.
The part that separates the toy from the product is reorg handling. Notify instantly at inclusion and a reorg makes you a liar; notify only at finality and every payment feels slow. The honest design is tiered, mirroring receipt-is-not-settlement on the outbound side: a tentative payment detected at inclusion, a final payment confirmed at finality, and — the branch nobody builds until it burns them — a retraction path when the tentative event vanishes from the canonical chain. Notification state becomes a small state machine, not a fire-and-forget send.
How it works
Watcher, queue, tiered delivery, and a reorg drill.
PoC
On anvil: a watcher (viem watchEvent or the Ponder pipeline) feeding a queue table { event, blockHash, tier, notifyState }. Delivery worker sends payment detected on inclusion and upgrades to confirmed after N blocks or finality; every message carries an id so a later retraction can reference it. Then the drill: anvil snapshot, include the payment, notify; revert and re-mine without it; the watcher sees the canonical chain no longer contains the event and the pipeline sends the retraction (or edits the in-app notification state). Tests: no duplicate sends across watcher restarts, retraction fires only for tentative-tier messages, finalized notifications are never retracted.
What it proves
Outbound truthfulness is a state machine, and inclusion-tier messages are conditional statements — the product face of probabilistic settlement. Pairs with the-app-reads-a-projection (inbound honesty) to complete the loop: what the user is told, in both directions, is an application-layer contract about chain state.