How a slot gets claimed or rolls on
An offer lands in a customer’s texts: “A 2:00pm colour opened up today — tap to claim, 10 minutes.” There’s a claim link. What happens when she taps it? And what happens if she doesn’t? This post walks through the three things that can happen to a live offer — claim, decline, time-out — and how a single conditional write makes sure exactly one person ever gets the slot, with the chain state and the audit trail staying in sync.
Key takeaways
- Three outcomes per offer: claim (book the slot), decline (roll on now), time-out (window ends, roll on).
- Claiming is one conditional write to DynamoDB — it succeeds for exactly one person.
- Any later click on a filled slot gets a polite “this slot was just taken” message.
- A time-out fires from an EventBridge Scheduler timer and rolls the offer to the next eligible person.
- The claim link is a Function URL carrying a short-lived token tied to one offer.
Three things can happen to a live offer
Outcome 1: claim (the one that fills the chair)
The customer taps the claim link. It points at the claim-handler Function URL and carries the short-lived token minted in Part 4, which names exactly one offer: (slot_id, offer_seq). The handler does the most important thing in the whole system: one conditional write. It updates the slot’s row in DynamoDB to status = booked, booked_by = entry_id with a condition that the write only succeeds if the slot is still open and this offer is still the live one.
If the condition holds, exactly one thing happens, atomically: the slot flips to booked. The handler then confirms to the customer (“You’re confirmed for 2:00pm today — see you then”), cancels the window timer so no roll-on fires, marks the token spent, and updates the Drive list via the Sheets API so staff see the chair is filled. If the condition fails — because someone else’s claim landed a half-second earlier, or the offer already rolled on — the write simply doesn’t apply, and the customer sees a calm page: “Sorry, this slot was just filled. You’re still on the waitlist for the next one.” No error, no double-booking, no two people sent to the same chair. The whole guarantee rests on that single conditional write: a database either accepts it or rejects it, and only one can win.
Outcome 2: decline (the polite no)
Every offer carries a second link: “Can’t make it.” Tapping it is the customer doing the system a favour — instead of letting the ten-minute window run down, they free the slot to roll on immediately. The decline link hits the same Function URL with a decline action. The handler marks this offer declined in wl-offers and fires a roll-on event straight to the engine, which picks the next eligible person and sends a fresh offer with a full window. The person who declined stays on the list for future slots unless they ask to come off.
Decline matters because it shortens the fill time. On a busy Saturday, the difference between rolling at ten minutes and rolling the instant someone says “not me” can be the difference between filling the 2pm and losing it. It also keeps the list honest — a quick decline is a clearer signal than silence.
Outcome 3: time-out (nobody tapped)
The most common outcome, especially for the first person offered: they’re busy, the phone’s in a bag, the offer sits unread. That’s fine — it’s exactly what the window is for. When window_ends_at arrives, the EventBridge Scheduler one-off rule armed in Part 4 fires. It re-reads the slot. If the slot is still open (nobody claimed), it marks this offer timed_out in wl-offers and rolls on: the engine’s “roll on” move sends the same slot to the next eligible person with a brand-new window. If the slot is no longer open — somebody claimed it in the last few seconds — the timer sees that and does nothing, because the claim already cancelled it (and even if a stray timer fires, the slot isn’t open, so the roll-on is a no-op). If the eligible list is exhausted or the per-slot try cap is hit, the slot is handed back to staff with a note instead of rolling further.
There’s a deliberate ordering here that keeps things safe: a claim cancels the timer, but the timer also re-checks the slot status, so the two can never both act on the same open slot. The conditional write is the backstop — even in the worst race, only one booking can land.
Every outcome is logged, every fill is explainable
The wl-audit table records every claim, decline, and time-out with the slot, the entry, the outcome, and a timestamp. Pull up a busy Saturday later and you can reconstruct exactly what happened to each freed slot: who was offered first, who declined, who timed out, who finally claimed, and when each link fired. If a customer says “I tapped claim and it said taken”, the log shows the winning claim landed eight seconds before theirs — the system did exactly the right thing, and you can prove it.
That explainability is the quiet payoff of doing the booking with one conditional write and logging every step. The fast path is simple — tap, book, confirm — and the edge cases (two taps at once, a late tap on a filled slot, a timer that fires just after a claim) all resolve to the same safe answer without anyone having to reason about them in the moment.
Next post: the cost breakdown. The whole pipeline above runs in coffee-money territory at SMB volume; Part 6 explains exactly where the dollars go.
All posts