Idempotency
Overview
Network calls can fail in ambiguous ways. A connection can be dropped after your request has been received and processed by WatchEye but before the response reaches you. Without help from the server, retrying the request would risk creating duplicate records, double-billing your account, or sending the same downstream request twice.
To make retries safe, the WatchEye API supports the Idempotency-Key request header. When you supply this header on a write request, WatchEye guarantees that the same key sent more than once will only do the underlying work once. Any subsequent request with the same key returns the original response, byte-for-byte, with no new database changes and no new billable charges.
Sending the header is always optional. If you do not send it, each request is treated as a brand new request.
Generating a Key
The Idempotency-Key value is a string of your choosing, up to 255 characters. It must be unique per logical operation - typically you generate a fresh UUID per request from your application:
Idempotency-Key: 8c4d3a18-2f63-4f9a-9f3a-9b1f5a7c2d4f
Two different operations should never share the same key. Reusing a key for a different request body is treated as a client error (see below).
Which Endpoints Honour the Header
The header is honoured on POST requests only. GET, PATCH and DELETE are conventionally idempotent at the HTTP level already (re-sending the same PATCH or DELETE produces the same end state), so the header is silently ignored on those methods.
If you send the header on a method where it has no effect, the request is processed normally and no error is returned.
Behaviour
For a POST request that includes the header, one of the following will happen:
-
First request - WatchEye records the key, runs the request, charges any applicable fees, returns the response, and remembers the response for 24 hours.
-
Retry with the same key and same body - WatchEye returns the original response, verbatim (same status code, same body, same
api_reference). No new work is done and no new charge is applied. The replay is identifiable by an additional response header:Idempotent-Replay: true -
Retry with the same key but a different body - WatchEye returns
422 Unprocessable Entitywith the message:{ "message": "Idempotency-Key was reused with a different request body." }This is a programming error in your client - either generate a new key for the new request, or send the same body as the original.
-
Concurrent retry while the first request is still in flight - WatchEye returns
409 Conflictwith the message:{ "message": "A request with this Idempotency-Key is already being processed." }Wait for the first request to complete (a few seconds at most for normal calls), then retry.
-
Original request failed (non-2xx) - The key is released. You can re-send with the same key, and the request will run fresh. This is intentional: if validation failed the first time and you have fixed your payload, your retry should run as a normal new request.
Detecting a Replay
Every replayed response includes the header:
Idempotent-Replay: true
Fresh responses do not include this header (the header is absent rather than false). If you ever need to know whether you are looking at the original outcome or a cached replay - for example, when debugging "why didn't this request seem to do anything?" - check for this header in the response.
This is especially useful for asynchronous endpoints such as POST /v1/monitors/{uuid}/run, where the response body alone does not tell you whether a new background job was actually queued or whether you are looking at the cached body from an earlier call.
Storage Window
A successful (2xx) response is remembered for 24 hours. After that the entry is discarded; a request with the same key after the window will be treated as a brand new request. In practice this is far longer than any reasonable client-side retry window.
Worked Example
A typical "create entity then retry on network error" flow:
POST /api/v1/programs/9c3e0a8e-2b9f-4f0e-8d1a-1e2b3c4d5e6f/entities HTTP/1.1
Authorization: Bearer wch_xxxx|secret
Idempotency-Key: 8c4d3a18-2f63-4f9a-9f3a-9b1f5a7c2d4f
Content-Type: application/json
{ "entity_type": "individual", "first_name": "Jane", "last_name": "Doe", "risk_level": "low" }
If the response never reaches you, replaying the identical request returns the originally-created entity, not a duplicate:
HTTP/1.1 201 Created
Idempotent-Replay: true
Content-Type: application/json
{ "data": { "uuid": "...", ... }, "api_reference": "..." }
Best Practices
-
Generate keys per logical operation, not per HTTP request: if your retry loop wraps the HTTP call, the same key must be used on every attempt of that loop. A new key per attempt defeats the entire mechanism.
-
Use UUIDs: They are easy to generate, globally unique by construction, and have no information leakage.
-
Treat the key as opaque: WatchEye stores a hash of the key, not the key itself. Do not depend on parsing the key on either side.
-
Do not reuse a key for a different operation: Always generate a fresh key when the request body changes.
-
Always retry on network failures with the same key: This is what the header is for. You cannot accidentally double-charge yourself.
Testing with API Clients (Postman, Insomnia, etc.)
Many GUI HTTP clients automatically attach a sticky Idempotency-Key to saved POST requests. The key is generated once when you first send the request and is then re-used on every subsequent click of Send until you regenerate or remove it. Combined with the 24-hour replay window, this means a single "test" request can keep replaying the same cached response for the rest of the day, even though it looks to you like you are submitting a fresh request each time.
The symptoms are easy to mistake for a server-side problem:
- The response body keeps showing the same
pending/createdstate from your first call. - The
api_referenceUUID is identical across every response (it should be different on every real request). - No new database records appear and, for async endpoints, no new background job is queued.
- The
Idempotent-Replay: trueresponse header is present.
If you see those symptoms while testing:
- Open the request's Headers tab in your client.
- Either remove the
Idempotency-Keyheader entirely (it is optional) or regenerate its value before each Send. - Confirm by checking that
api_referencechanges between calls and thatIdempotent-Replayis no longer in the response.
For production traffic the sticky key is exactly what you want - it makes "retry on network error" automatically safe. It is only the interactive "click Send again" testing workflow where it surprises people.