Preventing Double-Booking in Concurrent Zapier Workflows
Use atomic storage locks, availability re-checks, and lock expiry to prevent concurrent Zap runs from reserving the same resource.
Overview
It applies when:
- Multiple Zap runs can execute at the same time.
- Each run performs a check-then-write sequence against a single slot or unit.
- Two concurrent runs can both pass the availability check and both write, resulting in a double-booking.
After reading this article, you will understand the concurrency problem, why a naive availability check is not safe, and how to guard the write with a storage lock, a short delay, and a post-write availability re-check.
This article does not cover building a complete booking system, and it does not cover Zapier-specific triggers, actions, or app configuration in detail.
Key concepts
Why Zapier has no concurrency control
No concurrency control
Zapier does not expose a "run one at a time" or per-Zap mutex setting. When a trigger fires multiple times in quick succession, the resulting runs can execute in parallel. This is usually desirable for throughput, but it becomes a problem when a workflow does a check-then-write sequence against a shared resource.
Approximating a lock
Because Zapier offers no native lock, the workflow must approximate one. The approach in this article uses a storage-based lock to serialize the write, a short delay to widen the effective critical section, and a final availability re-check after the lock is held to make stale reads harmless.
Related capability
This pattern falls under Octacer's automation capability — production-grade workflows that execute repetitive operational work with consistent, auditable behavior. When embedded in a broader system that also needs to interpret unstructured inputs or route edge cases, an Octacer AI system can be added, but the deterministic lock-and-recheck pattern described here is the correct baseline and should be implemented before any AI layer is considered.
The pattern that causes double-booking is:
-
1
Read
Read the current availability of a slot or unit.
Decide whether the slot is still available.
Write a reservation or assignment. -
2
Decide
If two runs reach step 2 before either reaches step 3, both see the slot as available. Both then write, and the slot is double-booked. The race window is the time between the read and the write.
The role of storage, delay, and re-check
Prerequisites
Before implementing this pattern, confirm the following:
- A Zapier account with access to the Zaps that handle the booking or reservation workflow.
- Access to a storage mechanism that supports atomic write-if-empty operations. Common options include:
- A storage app available in Zapier (for example, a key-value store or a spreadsheet used as a lock table).
- A database with a conditional insert or an update that fails when the row already exists.
- An external API endpoint that can perform an atomic state transition.
- The workflow has a reliable way to identify the specific slot or unit being reserved. For example, a booking ID, a slot timestamp, or a unit SKU.
- An understanding of the Zap's failure and retry behavior, because concurrent runs may also collide on retry.
Understand the race condition
The unsafe pattern
The following sequence is unsafe and will double-book under concurrency:
| Run A | Run B | Result |
|---|---|---|
| Read: slot is available | ||
| Read: slot is available | ||
| Write: reserve slot | Slot reserved by A | |
| Write: reserve slot | Slot double-booked |
Both runs read availability before either writes. The second write overwrites or duplicates the first.
The safe pattern
| Run A | Run B | Result |
|---|---|---|
| Acquire lock for slot | Lock held by A | |
| Re-read availability while locked | Still available | |
| Write: reserve slot | Slot reserved by A | |
| Release lock | ||
| Acquire lock for slot — fails or blocks | ||
| Retry or reject | No double-booking |
The critical change: availability is read after the lock is held, so the read reflects the latest committed state. Run B never gets a chance to pass the availability check because Run A already wrote.
Procedure
Step 1 — Identify the slot identifier
Every booking, reservation, or assignment must have a unique, deterministic identifier that both the lock and the write use. This identifier must be computable from the trigger payload alone, so that two runs for the same slot produce the same key.
Examples:
booking:<DATE>:<TIME>slot:<EVENT_ID>:<SEAT_NUMBER>unit:<SKU>:<LOCATION>
The identifier is the lock key and the write key. If two runs for the same slot cannot derive the same key, the lock will not protect them.
Step 2 — Attempt to acquire the lock
Before performing the availability check, attempt to acquire the lock for the slot. The lock write must be atomic: if the lock key already exists, the write must fail.
Depending on storage:
- Database with conditional insert:
INSERT INTO locks (key, run_id) VALUES ('booking:2025-06-01:10:00', '<RUN_ID>')— the insert fails if the key already exists. - Key-value store with write-if-empty: store the run ID under the slot key. The operation succeeds only if the key is absent.
- External API: issue a request that claims the slot atomically; the API returns a conflict status if the slot is already claimed.
Step 3 — Add a short delay after lock acquisition
After acquiring the lock, add a short delay before the re-check. The purpose of the delay is to widen the window in which the lock is held, which reduces the chance that a concurrent run's lock attempt races the current run's write in a way that leaves both runs holding a lock.
The delay duration depends on the typical write latency of your storage layer. A reasonable starting point is a few hundred milliseconds to one second. The delay does not need to be long; it needs to be long enough that the subsequent write completes within the lock window.
Step 4 — Re-read availability while holding the lock
After the delay, read availability again — after the lock is held. Discard any availability value read earlier in the workflow. The re-read is the only availability check that matters.
| Check | When | Trusted? |
|---|---|---|
| First availability read | Before lock attempt | No — stale under concurrency |
| Re-read | After lock acquired, after delay | Yes |
Step 5 — Write the reservation or reject
- If the re-read shows the slot is available, write the reservation or assignment.
- If the re-read shows the slot is already taken, end the run without writing. Optionally emit an "unavailable" status or notify the requester.
The write must reference the same slot identifier used for the lock, so that any later run reads the new state.
Step 6 — Release the lock
After the write completes — or after the run is rejected — remove the lock so future runs can attempt acquisition again.
Configuration
Lock settings
| Setting | Purpose | Consideration |
|---|---|---|
| Lock TTL / expiry | Limits how long a lock can be held | Set above the worst-case write latency; long enough that a legitimate run completes, short enough that a crashed run does not block the slot permanently |
| Retry count | Number of lock-acquisition attempts for a single run | More retries reduce transient failures; too many retries increase load and delay |
| Retry delay | Wait between lock-acquisition attempts | Short for slot-based workflows; longer when the storage layer is shared or slow |
| Delay after lock | Widens the effective critical section | Tune against observed write latency; start small and increase only if double-bookings still occur |
Storage requirements
The storage layer must satisfy two requirements:
- Atomic lock acquisition — the lock write fails if the key already exists. Without this, two runs can both believe they hold the lock.
- Lock expiry — a failed or crashed run must not lock the slot forever. Use whatever expiry mechanism the storage layer supports.
Example flow
An illustrative Zap flow implementing this pattern:
- Trigger: New booking request.
- Step: Derive slot key from request (for example,
booking:<DATE>:<TIME>). - Step: Lock step — attempt to create lock record with the slot key as primary key.
- If lock creation fails, wait and retry up to the configured retry count. If still failing, end the run and mark the slot unavailable.
- Step: Delay — wait a short, configurable period.
- Step: Availability re-check — read the slot's current state.
- If unavailable, end the run and notify.
- Step: Write reservation — update the slot state or create the booking record.
- Step: Release lock — delete the lock record.
A request using field names for the lock write could look like:
{
"lock_key": "booking:2025-06-01:10:00",
"run_id": "<RUN_ID>",
"ttl_seconds": 30
}
The availability re-check response might look like:
{
"lock_key": "booking:2025-06-01:10:00",
"available": false,
"current_state": "reserved"
}
These examples are illustrative. The exact fields and response shapes depend on the storage layer and apps in your Zap.
Expected behavior
After implementing the pattern:
- Two concurrent runs for the same slot should result in exactly one successful write. The second run either fails to acquire the lock or re-checks availability after the first run has written and therefore rejects.
- Runs for different slots should not interfere with each other, because each uses a distinct lock key.
- A rejected run should not leave the reservation partially written.
- After the run completes, the lock should be released, allowing the next legitimate run to proceed.
Scope and limitations
This pattern protects against concurrent double-booking within a single Zap environment. It does not protect against:
- Multiple independent systems writing to the same slot without using the same lock mechanism. The lock only helps if every writer participates.
- Storage layers that cannot perform atomic lock acquisition.
- Long-running writes that exceed the lock TTL, which can allow a second run to acquire the lock while the first is still writing.
The pattern is a guard, not a substitute for a transactional database. If the booking workflow already writes to a database that supports row-level locking or conditional updates, prefer the database's native mechanism over a separate lock store.
Troubleshooting
Two runs both report success, and the slot is double-booked
Likely cause: The lock step was not atomic, the availability check still ran before the lock, or the two runs used different slot keys.
Check: Confirm that the lock write fails when the key already exists rather than overwriting it. Confirm that the availability re-read happens after the lock and after the delay. Confirm both runs derive the same key from the same payload.
Resolution: Fix the lock operation to be conditional, and move the trusted availability read to after the lock is held.
A slot is permanently locked and no new bookings can be made
Likely cause: A run crashed or failed after acquiring the lock and never released it.
Check: Inspect the storage layer for lock records that are older than the expected run duration.
Resolution: Add a lock TTL or expiry. If the storage layer does not support expiry, remove stale lock records manually and add a cleanup step to the workflow.
The lock step fails intermittently even when the slot is available
Likely cause: The previous run's write is still in progress, or the lock TTL is too short.
Check: Compare the lock TTL against the observed write latency in your storage layer.
Resolution: Increase the lock TTL and configure retries on the lock step.
Runs for different slots block each other
Likely cause: The lock key is not unique per slot, or the lock write uses a shared key.
Check: Confirm that the lock key includes the slot identifier and nothing that is shared across all runs.
Resolution: Derive the lock key purely from the slot identifier in the trigger payload.
Related
- Availability re-check logic and state handling in booking workflows
- Retry and failure handling for storage operations in Zapier
- Deterministic automation vs. AI decision systems — when a rules-based guard such as this one is sufficient, and when an AI layer adds value
Was this article helpful? Thanks for your feedback.
Ready to build your first automation?
Get started with Octacer and transform how your team works.
Schedule Consultation