Google Calendar automation: prevent double bookings
Two clients can request the same time slot at the same moment: here is how to design Google Calendar automation that prevents double bookings.
Two clients can request the same time slot at the same instant, and Google Calendar automation that does not handle this concurrency will eventually create two appointments for a single slot. The problem does not come from the API itself, but from the order in which your system reads availability, writes events and confirms to the client. This article details an application-level design that reduces this risk: fresh reads, temporary holds with expiration, stable identifiers and confirmation only after proof of writing.
Why double bookings happen
The typical scenario is simple. Client A checks your availability, thinks for a few minutes, then confirms. Meanwhile, client B queries the same calendar, sees the slot still free and books it. If your automation simply reads the calendar once and then inserts an event, it validates both requests with the same outdated information.
This problem exists with a classic form just as much as with an AI appointment agent that talks to the client before booking. The conversation even adds delay: the longer the dialogue lasts, the more likely the availability read at the start is wrong at the end. Concurrency is not rare in an SME with several intake channels; it is structural as soon as two people can reach the same slot.
You therefore need to separate three moments: reading availability, writing the event and sending confirmation to the client. A double booking always stems from a decision made between the first and the third moment, based on data that changed in between.
What the Google Calendar API guarantees and does not
The Calendar API lets you create events with an explicit identifier of your choosing. This is useful: if your system generates a deterministic identifier for a given request, a retry after an error will not create two distinct events for the same intent. You can review the official documentation on creating events for this mechanism.
However, the API does not provide a global transaction between your system and the calendar. There is no operation that says "book this slot only if it is still free, atomically with your own database." An availability read followed by a write remains two separate operations, with an interval during which another client can act. Claiming that the API alone automatically locks the slot would be wrong: your application architecture must close that gap.
This limitation is not specific to Google. It comes from the distributed nature of systems: your database and Google's calendar are two distinct systems with no shared memory. Any booking design must therefore assume this interval exists and make it harmless, rather than hoping it does not.
The final availability read right before the write
The first measure is to never decide based on availability read at the beginning of the conversation. Re-read slots at each decision step, and especially immediately before the write. A slot offered to the client must be revalidated at the moment they say yes.
Concretely, if a client starts a request at 2:05 PM and confirms at 2:09 PM, your system must re-read the calendar at 2:09 PM, not reuse the 2:05 PM state. If the slot was taken in the meantime, you detect it before writing and offer an alternative rather than a conflicting booking.
This re-read does not remove all risk: there remains an interval, however short, between the re-read and the write. It does, however, sharply reduce the exposure window, especially when combined with the temporary holds described below. If you work with clients in different zones, the re-read should also check time-zone consistency, a point we cover in our guide on the Google Calendar assistant and time-zone mistakes.
Application-level temporary holds
A temporary hold is an intermediate state in your own system: the slot is marked "being booked" for a given client, for a limited duration. During that period, the automation refuses to offer the slot to other clients, even if it still appears free in the calendar.
The flow works as follows:
- The client picks a slot. Your system creates a temporary hold, say for ten minutes, with a unique identifier.
- As long as the hold is active, that slot is excluded from the availability offered to other clients.
- If the client confirms within the deadline, you write the event to Google Calendar and turn the hold into a firm booking.
- If the deadline expires without confirmation, the hold is released and the slot becomes available again.
The duration depends on your client journey. A conversation with an agent may need a few minutes to ask qualification questions; a hold of five to fifteen minutes is a usual order of magnitude, to be adjusted to your cases. Too short, it frustrates hesitant clients; too long, it blocks slots for requests that will never complete.
One caveat: the temporary hold lives in your system, not in the calendar. Until you have written the event, Google Calendar knows nothing about this intent. That is precisely why it must have an automatic expiration, otherwise a crash of your system would leave slots blocked indefinitely.
Expiration and automatic slot release
Expiration is the mechanism that makes the temporary hold safe. Each hold carries an expiration time; a periodic process, or a check on every availability read, releases expired holds.
Two classic mistakes are worth avoiding. The first: releasing the hold only at the end of the client conversation. If the client closes their browser, the conversation never formally ends, and the slot stays blocked. Expiration must be independent of client behavior. The second: not handling the case where expiration occurs while the client is confirming. If the client clicks "confirm" at the very second the hold expires, your system must revalidate availability before writing, not assume the hold is enough.
A robust approach makes the write conditional: at confirmation time, the system checks that the hold is still valid and that the calendar still shows the slot free. If either condition fails, you fall back to offering an alternative. This double check costs one extra read, but it covers the most damaging concurrency case for the client.
Stable identifiers and retry after error
Network errors and retries are part of everyday life for an automation. If your system sends an event creation request and the response is lost, it does not know whether the event was created. Blindly retrying risks creating a duplicate.
That is where the explicit event ID becomes decisive. By generating a deterministic identifier yourself for each booking intent, a retry with the same identifier does not create a second event: the API recognizes the already-used identifier. We cover this mechanism in detail in our article on workflow automation, retries and duplicates, which applies directly to appointment booking.
The practical rule is simple: one identifier per business intent, not per technical attempt. If the client reschedules an existing appointment, treat that change as a new business operation with its own deduplication key, while retaining the identifier of the event to update. A reschedule does not, by itself, justify creating a second appointment. Our guide on correctly rescheduling and cancelling appointments with an assistant details this distinction between creating, updating and cancelling.
Client confirmation only after proof of writing
The most reputationally damaging mistake is telling the client their appointment is confirmed when the calendar write failed. Confirmation must therefore be conditioned on proof: the API response confirming event creation, with its identifier.
The correct flow is: availability re-read, event write, response verification, and only then sending confirmation to the client. If the write fails, the client receives an alternative or rescheduling message, never a fictitious confirmation. This rule also applies to internal notifications: your team must not block a slot based on an unwritten intent.
This proof requirement echoes a broader principle: the transport connection, the provider's authorization and the proof of operation are three different things. A request sent successfully is not a successful operation; only the service's response attests to it. Design your client messages around this distinction, not around the optimism of the sending moment.
Decision table: what to do in each situation
| Situation | Risk | What to do |
|---|---|---|
| Slot requested, availability read minutes ago | Stale data | Re-read the calendar before any decision |
| Two clients on the same slot simultaneously | Double booking | Exclusive temporary hold for the first |
| Client hesitates beyond the hold deadline | Slot blocked too long | Automatic expiration and release |
| API response lost after event creation | Duplicate on retry | Explicit, deterministic event ID |
| Confirmation sent before verifying the response | Announced appointment that does not exist | Confirmation only after proof of writing |
| Expiration during client confirmation | Inconsistent state | Revalidate availability and hold before writing |
| Access revoked mid-process | Write impossible | Detect the failure, inform, do not confirm |
Main commented example: a busy Tuesday morning
Take a hypothetical, explicitly fictitious case. A practice offers 30-minute consultations. On Tuesday at 9:02 AM, client A requests the 2:00 PM slot through your conversational agent. The agent re-reads the calendar, sees 2:00 PM free, and creates a ten-minute hold, identifier "b47c2a6f19d84a92a3d18a5e62f70311", expiring at 9:12 AM.
At 9:05 AM, client B queries availability. Your system excludes 2:00 PM from the list, because A's hold is active. B picks 2:30 PM, with their own hold. At 9:10 AM, A confirms. The system checks: hold valid, calendar still shows 2:00 PM free. It writes the event with the explicit identifier "b47c2a6f19d84a92a3d18a5e62f70311", receives the API confirmation, then sends the confirmation message to A.
At 9:13 AM, B is still hesitating. Their hold expires at 9:15 AM. At 9:16 AM, they confirm 2:30 PM. The system revalidates: slot still free, hold expired a minute ago but the slot was not offered to anyone else in the meantime. It re-creates a short hold, writes the event, confirms. Now suppose a third client, C, requests 2:00 PM at 9:17 AM: the re-read shows A's event, and the agent checks the remaining availability and offers 3:00 PM or another day, without writing a conflict.
The key point of this example: at no moment was a decision made on stale data, and no confirmation was sent without proof of writing. The temporary hold served as the application-level lock where the calendar provides none.
An actionable procedure to secure your bookings
Here is a seven-step procedure to transpose into your own booking tool:
- Number the intents. Each booking request receives a unique, deterministic business identifier, reused on technical retries.
- Read before offering. The availability shown to the client is computed from a fresh calendar read, never from an old cache.
- Place a temporary hold. As soon as a client picks a slot, your system marks it provisionally booked, with a set expiration.
- Exclude temporarily held slots. Other clients do not see those slots, even if they are still free in the calendar.
- Revalidate at confirmation. At the client's yes, check the hold's validity and real availability before writing.
- Write with proof. Create the event with the explicit identifier, verify the API response, and only then confirm to the client and the team.
- Release on expiration. A mechanism independent of the client releases expired holds, and a log traces every transition.
Each step exists for a specific reason: steps 2 and 5 shrink the concurrency window, step 3 prevents concurrency between clients of your system, step 6 prevents fictitious confirmations, and step 7 prevents long-lasting slot blockage.
Testing edge cases: timeout, conflict, resynchronization, revocation
A booking design is only as good as its tests. Four families of tests deserve to be run before any production launch.
Timeout test. Simulate a lost response during event creation. Verify that the retry with the same identifier does not create a duplicate and that the client receives only one confirmation.
Conflict test. Simulate two clients confirming the same slot seconds apart. Only one should get the write; the other should receive an alternative, never a fictitious confirmation.
Resynchronization test. If someone edits the calendar manually while a hold is active, your system must detect it at revalidation. The synchronization mechanisms described in the Calendar sync documentation let you track calendar changes; what matters is that the revalidation at confirmation relies on recent state, not assumed state.
Revocation test. Revoke access during an ongoing booking process. The system must detect the write failure, not confirm to the client, and offer a clean retry once access is restored. A silent revocation that results in fictitious confirmations is the worst scenario.
These tests lend themselves well to a structured approach: our article on testing a conversational AI agent proposes a method for building reproducible scenarios, applicable to these four families.
Frequently asked questions
Can the Google Calendar API alone prevent double bookings?
No. The API can create events, with explicit identifiers useful for retries, but it offers no global transaction with your system. Preventing double bookings relies on your architecture: availability re-reads, temporary holds and confirmation after proof of writing.
What duration should a temporary hold have?
It depends on the client journey. A conversation with qualification may justify ten minutes, an online payment rather five. The criterion is the realistic delay between slot selection and confirmation, plus margin. Too long, the duration blocks slots for abandoned requests; too short, it forces revalidations and alternatives.
What happens if a client confirms exactly when their hold expires?
Your system must revalidate both conditions before writing: the slot's real availability in the calendar and the state of the hold. If the slot is still free, the booking can proceed, possibly after re-creating a short hold. Otherwise, the client receives an alternative, never an unproven confirmation.
Why generate the event ID yourself rather than let the calendar choose it?
Because a deterministic identifier tied to the business intent makes retries safe. If a response is lost and you retry, the same identifier designates the same intent and does not produce a second event. With a calendar-generated identifier, each attempt creates a distinct event and the duplicate becomes inevitable.
Should all manual calendar writes be blocked for the system to work?
No, but you must account for them. If your team adds appointments manually, the availability re-read and resynchronization must reflect that. The design presented here tolerates manual writes precisely because it never trusts an assumed state of the calendar: it re-reads, holds temporarily, then confirms on proof.