How access gets paused and restored
Pausing access is the one thing this system does that the customer actually feels, so it has to be exactly right: never early, never silent, and instantly reversible. This post is about the gatekeeper — the small entitlement check your app calls on every request — how it flips a subscription to paused only after the last retry has genuinely failed, and how a successful retry or an updated card restores access in the same second.
Key takeaways
- Access is gated by one small entitlement check your app calls —
dunr-gatekeeperbehind a Function URL. - Access stays granted all through the retry window; it pauses only after the final retry has genuinely failed.
- Pausing is reversible. A successful retry or an updated card restores access in the same second — no overnight batch.
- The gatekeeper reads state from
dunr-dunningand answers in single-digit milliseconds, cached at the edge of your app. - It never cancels. The strongest action it takes on its own is a pause; cancelling is always a human decision.
One question on every request
Everything in the previous four parts has been invisible to the customer. Pausing access is the one thing they actually feel — so it has to be exactly right: never early, never silent, and instantly reversible. All of that hangs on a single question your app already asks in some form on every request: is this customer allowed in?
In this system that question is answered by dunr-gatekeeper, a small Lambda behind a Function URL. Your app calls it — on login, on a protected API call, on a page that costs you money to serve — with a subscription id, and gets back one of two answers: granted or paused. That’s the entire contract. The gatekeeper reads the current state from the dunr-dunning table and translates it: active, retrying, and recovered all mean granted; only paused means paused. Because it’s a single keyed read, it answers in single-digit milliseconds, and your app caches the answer for a minute or two at its own edge so the check adds no noticeable latency.
Access survives the whole retry window
A subscriber whose card just blipped should not be locked out while you’re still trying to charge it. That would punish exactly the customers most likely to recover — the ones whose card expired or hit a temporary hold — and it would generate angry support tickets for people who are about to pay you anyway. So all through the retrying state, across all four attempts and the roughly ten days they span, the gatekeeper answers granted. The customer keeps full access while the retries and the dunning emails do their work quietly in the background.
Access flips to paused at exactly one moment: when the final retry fails. At that point dunr-retrier updates the row to paused and the next entitlement check returns paused. The customer now sees the update-card screen instead of the product — not an error, not a dead end, but a clear “your payment didn’t go through; update your card to restore access” with the same one-tap link from the emails. Pausing is deliberately the strongest thing the system does on its own, and it’s a reversible state, not a deletion.
Restore is instant, not overnight
The flip side of pausing carefully is restoring fast. Nothing frustrates a customer more than fixing their card and then still being locked out “while the system catches up”. There is no overnight batch here. Restoration happens on the same event-driven path as everything else:
- A retry succeeds. The processor fires
charge.succeeded; the front door routes it; the row flips to recovered; the next entitlement check returns granted. If the subscription was paused, the customer is back in the moment they refresh. - A card is updated. The customer taps the link and updates their card on the processor’s page; the
payment_method.updatedwebhook tells the retrier to attempt the charge now rather than waiting for the next scheduled slot. The moment that charge clears, access is restored — often within seconds of them entering the new card.
In both cases dunr-gatekeeper needs no special wake-up: it always reads the live row, so the instant the row says recovered, the answer is granted. The only lag is your app’s own short cache on the entitlement answer, which is why that cache is kept to a minute or two.
Pause is not cancel
It’s worth stating the boundary plainly, because it’s the guardrail that defines this system. The strongest action dunning recovery takes on its own is to set a subscription to paused — a reversible flag that gates access and shows an update-card screen. It never deletes the subscription, never tells the processor to cancel it, never wipes the customer’s data. Cancellation is a separate, deliberate act that a human takes in your billing tool when they’ve decided this customer really has gone. When that happens the processor fires subscription.cancelled, and the system simply stands down — cancels any remaining retries, stops the emails, closes the row. The arrow into cancelled is the one arrow in the whole state machine the system never draws by itself.
Design rules that shaped access
- One check, two answers. The app asks
dunr-gatekeeper; it replies granted or paused. - Granted through retries. Nobody is locked out while you’re still trying to charge their card.
- Paused only at the end. Access flips only after the final retry has genuinely failed.
- Restore is instant. A cleared charge or updated card restores access in the same second — no batch.
- Pause is reversible; cancel is human. The system never originates a cancellation.
- Every flip is logged.
dunr-logrecords each pause and restore with a before-and-after snapshot.
That’s the system end to end: caught, scheduled, emailed, paused, restored. The next post adds up what it all costs — and why the part that recovers the money is the cheapest part of the bill.
All posts