Fix RelayHook concurrent-call responses with UUID request IDs

Fix RelayHook concurrent calls: generate a UUID request ID per webhook call, store it in request context, and route responses by ID to prevent mixups.

Jul 28, 2026
Fix RelayHook concurrent-call responses with UUID request IDs
If RelayHook (or any webhook relay / request-response proxy) mixes up responses when two calls happen at the same time, the fix is almost always the same: create a unique request ID for every inbound request and carry it through every hop until you send the response back. In practice, that means generating a UUID, storing it with the request context, and refusing to “guess” which browser tab/session should receive a response.
Photo by Ilya Pavlov on Unsplash
Photo by Ilya Pavlov on Unsplash

What’s happening (the bug pattern)

When a relay tool is doing synchronous request/response handling in a browser (or a browser-adjacent runtime), concurrent requests can collide in ways that look like:
  • Request A comes in on Tab 1, Request B comes in on Tab 2
  • The relay processes both concurrently
  • The response intended for Request A gets delivered to Tab 2 (or vice versa)
This is typically caused by one of these implementation issues:
  • Shared global state (e.g., “current response handler”) that gets overwritten by the second request
  • Tab/session reuse where multiple calls are being mapped to the same “active tab” context
  • Race conditions in async code, where “the last listener wins”
  • Missing correlation key between the inbound request and the outbound response

The practical fix: UUID per request (and thread it everywhere)

Step 1: Generate a request ID at the edge

The moment the request enters your system, assign it a unique ID.
  • Prefer UUIDv4 (random) unless you have an existing deterministic ID from the upstream provider.
  • Treat this as a correlation ID for the whole lifecycle.
Example (pseudo-code):
requestId = uuid()

Step 2: Attach the ID to the request context

Store it in the same structure you use to track the call, for example:
  • in-memory map keyed by requestId
  • per-tab or per-session store (but still keyed by requestId)
  • persisted store if you need recoverability
The key requirement: never use “tab” alone as the identifier when more than one request can be in-flight.

Step 3: Propagate the request ID through every hop

Thread it through:
  • logs
  • any internal events/messages
  • downstream calls (headers, query params, payload fields)
  • the response routing layer
Common patterns:
  • X-Request-Id: <uuid> header
  • request_id field in the JSON payload

Step 4: Route the response by request ID, not by tab

When the response is ready, lookup the exact handler/connection using the requestId.
  • If the handler is missing (tab closed, timeout, etc.), fail safely and log it.
  • If there are multiple handlers (shouldn’t happen), treat it as a bug and do not deliver a response.

Step 5: Add timeouts + cleanup

Every in-flight request should have:
  • a timeout window
  • cleanup logic to remove it from your map/store
Without cleanup, the system can leak memory and eventually degrade.

Common pitfalls (and how to avoid them)

Pitfall: “Session ID” that’s shared across concurrent calls

If multiple requests share a session ID, you still can’t reliably route concurrent responses. Session IDs are useful, but they’re not granular enough.

Pitfall: One event listener for multiple in-flight calls

If your implementation uses a single “response event” listener that updates on every request, concurrency will break it.

Pitfall: Relying on timing

Anything like “whichever response finishes first goes to the currently active tab” is guaranteed to fail under real-world load.

Testing checklist (to prove it’s fixed)

Use a simple concurrent test plan:
  1. Send 10–50 concurrent requests to the same hook/endpoint.
  2. Ensure each request includes a unique request ID (or confirm the relay generates one).
  3. Return a response body that includes the request ID.
  4. Validate that every caller receives its own request ID back.
Recommended test assertions:
  • No duplicate IDs in responses
  • No missing IDs
  • No mismatched IDs
  • Stable behavior when:
    • a tab closes mid-flight
    • responses are delayed randomly (simulate jitter)
    • one request errors while others succeed

When to consider a different architecture

If you need strict reliability under high concurrency, move response routing out of the browser:
  • a lightweight server component that handles correlation and routing
  • a queue for processing + immediate 202 response to the sender
For a deeper walkthrough of webhook response constraints and why async automations in tools like Zapier or Pipedream can struggle with strict request/response contracts, see: RelayHook vs Zapier Webhooks: what's broken and how to fix it

Get help debugging your webhook routing

If you're seeing weird concurrency behavior in RelayHook (or any webhook relay) and want a clean, testable pattern for correlation IDs + response routing, book a free consulting call — we can help you design and implement it.