How a deposit payment gets confirmed
Money makes this the step that has to be exactly right. When the deposit clears, the payment provider posts a webhook — and this post is about trusting it: verifying its signature so a stranger can’t fake a payment, and flipping the booking to confirmed once and only once, however many times the event is delivered.
Key takeaways
- The payment provider posts a
payment.succeededwebhook to a second Lambda Function URL — the money surface, kept separate from the booking intake. - The webhook’s signature is verified against a secret in Secrets Manager before anything runs, so a stranger can’t fake a paid deposit.
- The event is recorded idempotently: providers re-deliver webhooks, so the same event id is only ever processed once.
- The booking is flipped PENDING → CONFIRMED with a conditional write, so a duplicate delivery can never confirm twice or fire a second confirmation.
- The system reads the money from the signed webhook, never from the customer’s browser — the redirect back to your site is a courtesy, not the source of truth.
The step that has to be exactly right
When the customer pays their deposit, the booking has to become real — and because money is involved, this is the step with the least room for error. Two failures matter here and neither is theoretical. If the system could be tricked into believing a deposit was paid when it wasn’t, someone could hold your busiest slots for free. And if a single genuine payment could confirm a booking twice — or refund twice, or email the customer twice — the business’s books and inbox both go wrong. So the confirmation path is built around two guarantees: only the real payment provider can trigger it, and a given payment confirms the booking exactly once.
The confirmation comes in as a webhook. The moment the deposit clears, the payment provider posts a small signed message — payment.succeeded, with the payment id, the amount, and a reference tying it back to the booking — to a Lambda Function URL. This is the second Function URL in the system, deliberately separate from the booking intake in Part 2. Keeping the money surface apart from the booking surface means each has exactly the permissions it needs and no more: the webhook function can confirm bookings but can’t create them, and the intake function can create bookings but can’t confirm payment.
Prove the provider sent it
A Function URL is public, so the very first thing the webhook function does is verify the provider’s signature. Payment providers sign every webhook with a secret shared only with you; the signature is a hash of the exact request body and a timestamp. The function recomputes that hash using the signing secret held in Secrets Manager and compares it — in constant time — against the one on the request. If they don’t match, or the timestamp is stale (which blocks an attacker replaying an old captured event), the request is dropped with a 400 and nothing else happens. Without this check, anyone who found the URL could post a fake “deposit paid” and confirm bookings for free; with it, only messages genuinely from your provider get any further. This is why the money never comes from the customer’s browser: a redirect back to your site after payment can be forged or simply lost, so the signed server-to-server webhook is the only thing the system trusts.
Exactly once, even when it arrives twice
Payment providers guarantee they’ll deliver a webhook at least once, which means they will sometimes deliver it more than once — a network blip, a slow response, and the same payment.succeeded is sent again. The system has to treat that as a fact of life, not an error, and still confirm the booking exactly once. It does this in two layers. First, it records the payment event id with a conditional write to a payments table — the write only succeeds if that event id hasn’t been seen before, so a re-delivery finds the record already there and stops, cleanly acknowledged. Second, and this is the real backstop, the confirming write to the booking is itself conditional: it moves the booking to CONFIRMED only if it is currently PENDING. If a duplicate slips through, or two deliveries race, the second one finds the booking already CONFIRMED, the condition fails, and it does nothing. One payment, one state transition, one confirmation email — guaranteed by the database, not by hoping the event only comes once.
What confirmation actually does
Once the conditional flip succeeds, the booking is genuinely confirmed and three small things follow, all downstream of that single state change. The PENDING hold becomes a firm booking — the slot is now sold, not just reserved. The customer gets one short confirmation, again phrased by a Bedrock call but with the facts (the slot, the deposit paid, that it comes off the bill) injected by code. And the release sweep from Part 4 will now leave this booking alone, because it only ever looks at bookings that are still PENDING past their deadline; a CONFIRMED booking is simply invisible to it. Because every one of these actions hangs off the exactly-once state transition, none of them can happen twice: no double confirmation, no doubled figure in the day’s takings, no second text to a customer who paid once.
Edge cases are handled deterministically, not guessed at. A payment for an amount that doesn’t match the expected deposit, a payment whose reference points at a booking that’s already been released, or a rare payment.succeeded for a booking the system has no record of — each is recorded and routed to a person rather than auto-confirmed, because a mismatched payment is exactly the kind of thing a human should look at. The webhook is always acknowledged with a 200 once it’s safely recorded, even in these cases, because a provider that doesn’t get an acknowledgement will keep retrying — and the whole point of the idempotency is that those retries are harmless.
Design rules that shaped confirmation
- A separate money surface. The payment webhook has its own Function URL and its own tight permissions, apart from the booking intake.
- Verify before you believe. The signature and a fresh timestamp are checked first — a forged or replayed webhook never confirms a booking.
- Trust the webhook, not the browser. The signed server-to-server event is the source of truth; the post-payment redirect is only a courtesy.
- Record the event once. A conditional write on the payment event id makes re-delivery a harmless no-op.
- Confirm on a condition. The booking moves to CONFIRMED only if it’s still PENDING, so one payment transitions it exactly once.
- Mismatches go to a person. Wrong amounts or unknown references are recorded and escalated, never confirmed on a guess.