Skip to content

How to prevent double bookings with online payments: holds, webhooks and refunds

Hold the slot briefly, expect every payment notice twice, and decide in advance what happens when money arrives for a slot someone else already booked.

Two payments reach for the same calendar slot; a short hold decides which one gets it.
In this article8 sections
  1. Hold the slot, briefly
  2. Expect to hear about every payment twice
  3. Book the slot in the same transaction
  4. Decide what happens when the slot is gone
  5. Freeze the price, and store it in cents
  6. Don't make checkout wait for the meeting link
  7. Questions to settle before you build
  8. Sources

Two captured payments don't buy two copies of a mentor's 6 pm slot. Consider a student who pays after their checkout hold expires, while another student has already booked the hour. The booking system now owes one student a session and the other a clear answer about their money.

We built the booking flow for MentorAID Connect, a UPSC mentorship platform in India. Its design has to handle this late-payment case without creating a second booking. A pending refund stays an admin task until someone completes it.

  1. Minute 0
    Student A opens checkoutThe slot is held for 20 minutes while they pay.
  2. Minute 20
    The hold expiresNobody has paid, so the slot counts as free again.
  3. Minute 22
    Student B books and paysThe slot, the payment and the session are confirmed together.
  4. Minute 25
    Student A's payment arrivesThe money is real and the slot belongs to someone else.

Hold the slot, briefly#

Session length
60 min
Booking window
7 daysHow far ahead slots open
Checkout hold
20 min

When a student starts checkout, MentorAID holds the slot for 20 minutes. Choose a hold long enough for the slowest payment method your users rely on, and short enough that an abandoned checkout frees the slot quickly. Twenty minutes suits this product; yours may need a different number.

Expired holds don't need a cleanup job. MentorAID resolves them lazily: whenever a request reads the slot, it treats a hold older than 20 minutes as free. That removes a scheduled job you would otherwise have to monitor.

Expect to hear about every payment twice#

Razorpay tells you about a payment in two ways. The browser gets a result when checkout closes, and your server gets a webhook. Either can arrive first, and they can arrive at almost the same moment. Razorpay's webhook documentation also says your endpoint may receive the same event more than once and calls that expected behaviour. Each event carries a unique x-razorpay-event-id header so you can spot repeats.

So in MentorAID both paths run the same three steps:

  1. VerifyCheck Razorpay's signature on what arrived.
  2. ConfirmFetch the order and payment back from Razorpay: captured, right amount, right currency, this order.
  3. Record onceInsert the payment record behind a unique constraint, so only one insert can win.

The third step is the easy one to get wrong. The tempting version reads first: "is there already a record for this payment? No? Then insert one." Two requests arriving together can both read "no" and both insert. A unique constraint closes that gap, because the database lets exactly one insert through and rejects the other.

The project's review guidelines put it in one line: duplicate protection needs a database constraint, never a read followed by a write.

Book the slot in the same transaction#

The payment record, the slot lock, the booking and the session all happen in one database transaction. Either all of them commit or none do.

When the browser and the webhook race, the slower one hits the unique constraint, finds the booking the faster one committed and returns that. The student sees one confirmation. The mentor sees one session.

Decide what happens when the slot is gone#

Back to minute 25. The first student's hold has expired, the second student has booked and paid, and the first student's payment arrives.

MentorAID marks that payment REFUND_PENDING and the order as paid. It never creates a second booking for the slot. REFUND_PENDING is a state for a person to act on; it does not send money back by itself.

If you build something similar, weigh three options:

  • a refund through the payment provider;
  • a credit to the student's wallet;
  • an offer of the next free slot.

Whatever you pick, make sure someone gets alerted, and write down how long the student waits.

Freeze the price, and store it in cents#

Each order and booking keeps its own copy of the price and slot time at purchase. When an admin raises a mentor's price next week, last week's bookings and receipts stay as they were.

Amounts live as integers in cents, so $14.99 is stored as 1499. Integers don't round, so the same numbers always add up to the same total.

Times are shown and interpreted in IST, so a 6 pm slot means 6 pm in India for the student, the mentor and the server.

Creating a Zoom meeting is a call to someone else's API, and it can fail. In MentorAID the booking confirms first. A background job on Inngest then creates the meeting, retries on failure, marks it ready or failed, and emails the student and the mentor separately.

There is one gap retries can't cover: the server saves the booking and stops before it sends the "create a meeting" event. An hourly sweep looks for sessions that have waited more than ten minutes for a meeting and triggers creation again. A separate scheduled job ends each meeting, with a 60-minute cap.

Questions to settle before you build#

  1. How long does your slowest common payment method take, and how long should a hold last?
  2. What happens to money that arrives for a slot that's gone: refund, credit or reschedule?
  3. Who gets alerted when a payment needs a person, and how fast must they act?
  4. Which time zone does a slot belong to when your expert and your customer are in different places?
  5. What happens to a paid booking when the expert cancels?

Settle the last two on paper before launch. They are expensive to change once real bookings exist.

If you run coaching, consulting, tutoring or a clinic and your bookings still live in WhatsApp and a spreadsheet, tell us how a booking works today. We'll map the flow with you before anything gets built. The MentorAID case study shows the rest of the platform.

Sources#

  • Razorpay, validating and testing webhooks, including duplicate delivery and the event ID header.
  • MentorAID's booking rules and review guidelines, from the project source, checked 4 October 2026. The timeline above is an illustrative case the design handles, not a reported incident.