Idempotency
Network failures, timeouts, and retries are facts of life. Without idempotency, retrying a POST that “looked” like it failed can create a duplicate record. The Idempotency-Key header lets you retry safely: BuildWorkPro replays the original response instead of executing the mutation a second time.
How it works
Section titled “How it works”Send Idempotency-Key: <uuid> on every POST, PATCH, or DELETE request. We strongly recommend a fresh UUID v4 per logical operation.
- First request with this key — The mutation runs normally. We cache the response (status + body) keyed by the
(tenant, key, body-hash)tuple. The body-hash is a SHA-256 ofmethod + "\n" + path + "\n" + JSON.stringify(body ?? null). - Repeated request with the same key + same body — We return the cached response. The mutation does not run again. Side effects (records created, webhooks fired) happen exactly once. Body-equality is checked via the SHA-256 hash above, so re-ordering JSON keys in an otherwise-identical body produces a different hash and is treated as a different operation.
- Repeated request with the same key + different body — We return
409 idempotency_key_reused. This catches bugs where you accidentally reused a key for a different operation. - After 24 hours — The cached response expires. Reusing the key after that is allowed (and treated as a new operation).
The dedup window is 24 hours from the moment of the first request — not 24 hours from the last replay. Keys are scoped to your tenant; a key from one tenant can never collide with another’s, even when both use the same string.
Worked example
Section titled “Worked example”Imagine your code is creating a contact, the network drops mid-response, and you don’t know whether the server received it. Just retry with the same Idempotency-Key:
KEY=$(uuidgen)
# First attempt — server processes it but the response never reaches youcurl -X POST https://app.buildworkpro.com/api/v1/contacts \ -H "Authorization: Bearer ${BUILDWORKPRO_API_KEY}" \ -H "Idempotency-Key: ${KEY}" \ -H "Content-Type: application/json" \ -d '{"firstName":"Jane","lastName":"Doe","type":"customer"}'
# Retry — same key, same body. Server returns the original 201 response# without creating a second contact.curl -X POST https://app.buildworkpro.com/api/v1/contacts \ -H "Authorization: Bearer ${BUILDWORKPRO_API_KEY}" \ -H "Idempotency-Key: ${KEY}" \ -H "Content-Type: application/json" \ -d '{"firstName":"Jane","lastName":"Doe","type":"customer"}'Both calls return the same 201 Created response with the same contact id. Only one contact exists. No webhook fires twice.
When to use a new key vs reuse
Section titled “When to use a new key vs reuse”- Same logical operation, retrying after failure? Use the same key.
- Two different operations, even of the same kind? Use different keys. Creating two contacts requires two keys.
- Driving a queue worker that processes events from your own system? A great pattern is to derive the key deterministically from your event id (e.g.,
crm-event-${eventId}) so retries from your queue map to the same idempotency key automatically.
Limitations
Section titled “Limitations”- Only mutations are idempotent (
POST,PATCH,DELETE).GETis naturally idempotent and ignores the header. - Cached responses are kept for 24 hours, then evicted. Don’t rely on idempotency replay for durable state — read the resource back if you need to confirm.
- Only successful (2xx) responses are cached. A failed mutation can be retried fresh.