How a booking requests a deposit
Before a deposit can be paid, the slot has to be safely held and the customer has to be asked. This post is about that first move: how a raw booking becomes a PENDING hold that can’t be sold twice, with a deadline attached and one well-phrased message carrying the payment link out the door.
Key takeaways
- The trigger is a booking posted to one Lambda Function URL — the intake surface — separate from the payment webhook, and with no API Gateway.
- The slot is held with a conditional write on the slot key, so the first booking wins and a second one can never take the same slot.
- A booking that loses the race for a slot is told so and offered an alternative — it is never silently double-booked.
- The hold is stamped with a deposit deadline, the clock the release sweep in Part 4 later reads.
- Only after the slot is safely held does one Bedrock call phrase the deposit-request message, with the payment link injected by code.
From a booking to a held slot
Everything starts with a booking. However it’s made — a form on the website, a reservation widget, or a member of staff keying in a phone call — the booking source posts a small HTTP request to a Lambda Function URL: who booked, when, and which resource (a table, a chair, a grooming slot). That POST lands on the intake function. There’s no API Gateway in front of it; a Function URL is a plain HTTPS endpoint on the function itself, which is all a booking post needs and the cheapest way to receive one. This is one of two Function URLs in the whole system — the other, for the payment provider’s webhook, is the subject of Part 3, and keeping them separate keeps their jobs and their permissions cleanly apart.
The intake function’s job is not to send a deposit request. Its job is to safely take the slot — because everything downstream, the deposit link, the confirmation, the release, is meaningless if two customers can both be holding the same table. Most of this post is about how that hold is made exactly once, and only then how the ask goes out.
Holding one slot, and only one
A slot is a scarce thing: one 8pm table for six, one 2pm chair with a particular tattoo artist, one Saturday grooming appointment. Two bookings can arrive for it within the same second — two customers on the website at once, or a form submit that fires twice. The system must let exactly one of them hold it. So the function doesn’t just check whether the slot is free and then write; a check-then-write has a gap where both bookings see “free” and both proceed. Instead it writes the hold with a conditional write to DynamoDB, keyed on the slot itself, that only succeeds if no hold already exists for that slot. The first write wins and creates the PENDING hold; any second write for the same slot fails the condition and is rejected. There is no window in which both can succeed, because the database, not the application, arbitrates.
A booking that loses that race isn’t dropped on the floor. It’s told, honestly, that the slot has just gone, and offered the nearest alternatives — the 8.30 table, the next chair, tomorrow’s slot. What it is never given is a silent double-booking, because a double-booked Saturday night is a far worse customer experience than “that one just went, how about this?”. The same conditional write is what makes the whole system safe to run at a busy business, where slots are contended and timing is tight.
Stamping the deadline
When the hold is written, the function stamps two things on the booking record: the state (PENDING) and a deposit deadline — the moment by which the deposit must be paid or the hold is forfeit. The deadline comes from the settings doc and suits the business: a restaurant might give until 6pm on the day, a tattoo studio 48 hours, a groomer 24. That timestamp is the single most important field for what comes later, because it’s the clock the release sweep in Part 4 reads: no deadline, no way to know when an unpaid hold has gone stale. Setting it here, at the moment the slot is taken, means the countdown starts the instant the customer’s intent is freshest.
Asking once, and asking well
With the slot safely held, the ask goes out — and this is the one place in the intake path where a model runs. A single Bedrock Haiku 4.5 call is handed only the facts it may use: the customer’s first name, the slot they booked, the deposit amount, and the business’s voice notes. It writes one short, warm message asking for the deposit — friendly, not threatening, because a deposit request that reads like a debt collector loses the booking it was meant to secure. Haiku is right for the job precisely because the task is small: a sentence or two, in a set tone, running on every booking, for a fraction of a penny.
The payment link itself is never written by the model. The model emits a placeholder like {{pay}}, and the code substitutes the real hosted-checkout URL afterwards. This matters more here than almost anywhere: a mistyped link on a deposit request doesn’t just read badly, it takes the customer’s money to nowhere or, worse, somewhere wrong. By keeping the link out of the model’s hands and injecting it deterministically — and validating that the finished message carries exactly one link, the amount, and a note that the deposit comes off the bill — the ask is always correct. If the model is slow or the draft fails a check, a fixed template goes out instead: plain, but always right, always on time. The model gives the request its warmth; the code guarantees the link and the number are exactly what they should be.
Design rules that shaped the intake step
- Two surfaces, kept apart. The booking intake has its own Function URL, separate from the payment webhook, with its own narrow permissions.
- The database arbitrates the slot. A conditional write on the slot key holds it exactly once; a check-then-write would leave a double-booking gap.
- Losing the race is handled. A booking that can’t take the slot is offered an alternative, never silently double-booked.
- Set the clock at the hold. The deposit deadline is stamped the moment the slot is taken — it’s what the release sweep reads later.
- Ask only after the slot is safe. Nothing is sent to the customer until the hold is written and the booking is PENDING.
- The link is the code’s job. The model phrases the ask; the payment URL and the amount are injected and validated deterministically.