Idempotency and Callback Security: Two Critical Integration Decisions
What is idempotency?
Idempotency means that sending the same request once or many times produces the same result. When a network timeout occurs the client retries; if the server treats the retry as a new operation, a second order, invoice or payment is created. Every endpoint that creates a record therefore needs a defined answer to retries.
How idempotency is implemented
The client generates a unique operation key for each business action and sends it with the request, keeping the same key when the user retries. The server stores the key together with the result of the request. When a second request arrives with the same key, no new record is created and the original result is returned. The retention period for keys is defined explicitly.
One caveat: if the key matches but the body differs, that is an error and should be rejected with a clear status rather than silently returning the first result, which would hide a genuine mismatch.
In HTTP, `GET`, `PUT` and `DELETE` are idempotent by definition while `POST` is not, so operation keys are mainly needed on `POST` endpoints.
Callback (webhook) security
Inbound notification calls change state in your system, which makes them an attack surface. Five essential measures:
- Signature verification: the sender should transmit a signature of the body computed with a shared secret, and you should compute and compare the same value.
- Timestamp and replay protection: apply a narrow time window and keep recently seen signatures for a short period.
- Verify against the raw body: the signature must be computed over the raw payload, before any JSON re-serialisation.
- Do not treat the notification as the source of truth: it only says that something happened. Amount, state and outcome should be re-read from the sender's API.
- Separate receiving from processing: accept the call quickly and enqueue it, then run business logic from the queue so the sender does not time out and retry unnecessarily.
"Exactly once" is a fallacy
In distributed systems you cannot guarantee that a message arrives exactly once. The realistic target is at-least-once delivery with a receiver that neutralises duplicates, which means the consumer also needs a duplicate check based on an operation key or event identifier.
Retry policy
Retry transient failures — network errors, 5xx, rate limits — with increasing wait times plus a random jitter. Do not retry permanent failures such as validation errors or authorisation failures; move them to a dead-letter queue instead. Log every retry, because reconciliation is impossible when you cannot see how often a request was attempted.
Values worth measuring
Integration health is tracked with three numbers: pending queue length, mean processing delay, and the count of records landing in the dead-letter queue. Their financial-reconciliation counterpart is covered in the [bank integration guide](/en/blog/bank-integration-guide). Without them on a dashboard, "the integration works" is an assumption.
Frequently asked questions
Can the server generate the operation key?
It can, but the benefit comes from generating it on the client. If the server generates it, the client cannot learn the key when the response is lost, and its retry becomes a new operation.
Is polling ever better than webhooks?
At low volume and with tolerance for delay, polling is simpler and more reliable. At high volume it wastes capacity, and a combined approach is common: webhooks for fast notification and periodic polling to catch missed events.
Which integration test scenario is skipped most often?
The lost-response scenario. A request that was processed successfully but whose response never reached the client is common in practice, and idempotency is not verified until it has been reproduced.