AWRA OpsHub Search

The Second Identical Request

Two mechanisms in this product stop a request being processed twice, and they disagree about almost everything — including whether an identical second request is a duplicate or a legitimate new one. Both are right for where they sit.

Integrations & Data AWRA OpsHub Team 12 min read

Our take

Deduplication is one of those problems where the correct answer depends entirely on what the caller is. For an integration posting into your workspace, an identical body arriving twice is almost always a retry after a timeout, and treating it as a replay is right. For an API a customer writes against, two identical adjustments an hour apart are two adjustments, and refusing the second would be a defect. So one mechanism synthesises a key from the request body when none is supplied, and the other does nothing at all without an explicit key. Neither is a weaker version of the other. The mistake worth avoiding is assuming a single deduplication policy can serve both, because the two safe defaults point in opposite directions.

Two identical requests are either one thing that arrived twice or two things that look alike, and nothing in the request itself distinguishes them.

What the two mechanisms do

The API Inbound integrations
Who calls it A customer or a developer writing against the public interface A configured integration posting into a workspace
With no key supplied No deduplication at all A key is synthesised from the credential and the request body
Key sources A standard header, or a field in the request A standard header, or a product-specific one
Time window Twenty-four hours None — the record persists
Same key, same body The stored response is replayed with its original status Reported as a replay to the caller
Same key, different body Refused as a conflict, with a named error code The record already exists; the differing hash is stored on it

Why the integration side invents a key

An integration is a program, and a program that posts the same body twice is nearly always retrying. It timed out, it did not see the response, and it is asking again for the outcome it never received.

So when no key is supplied, one is derived from the credential and the exact bytes of the body. Two posts from the same integration with the same content produce the same key, and the second is reported as a replay rather than processed again. That is the safe default in a context where a duplicate is expensive and a coincidence is unlikely.

The consequence is real and worth planning for

Two genuinely separate events with byte-identical bodies are indistinguishable from a retry, and the second will be reported as a replay. An integration that posts "adjust item X by 1" twice for two real occurrences has to make the two requests differ — a timestamp, a reference, an event identifier, or an explicit key of its own. This is not a limitation to work around so much as a contract to honour: an integration that sends identical bodies for distinct events is asking to be deduplicated.

And there is no expiry. The record of a request persists, so an identical body posted a month later is still recognised. That is deliberate for a machine caller and would be intolerable for a human one, which is exactly why the other mechanism does it differently.

Why the API side does nothing without a key

The public interface is called by people writing software against a documented contract, and identical requests are ordinary. Two stock adjustments of the same quantity, two identical notes, two identical records created an hour apart — all legitimate, all indistinguishable from each other by content.

So deduplication there is opt-in. A caller who wants exactly-once semantics supplies a key, and gets three guarantees for it.

  1. A repeat with the same key and the same body replays the original response

    Not a fresh success — the stored body and the stored status code. A retry after a timeout receives exactly what the first attempt produced, including its identifiers.

  2. A repeat with the same key and a different body is refused

    With a specific conflict status and a named error code. That is the case where a caller has reused a key for a different operation, and processing it would be worse than refusing.

  3. A key ages out after a day

    Long enough to cover any plausible retry and short enough that a key reused next week is treated as new. A caller generating a key per operation never notices; one generating a key per record eventually will.

Replaying the original response is the guarantee. Returning a new success is not.

The detail that makes a replay useful

A deduplication mechanism that recognises a repeat and returns a bare acknowledgement is only half a mechanism. The caller retried because it never saw the first response — and the first response is what contained the identifier of the thing that was created.

Storing the response body and the status code, and returning them again, is what turns deduplication into something a client can actually build on. The retry receives the same identifier the original attempt produced, which means the client ends up in the same state whether or not the first response ever arrived.

What each one records

The integration side keeps a log per request: the credential it arrived on, the key, a hash of the body, the address it came from, and — once processing finishes — the status, the response and the time it completed. That is a complete record of what an integration asked for and what it got, which is the first thing anybody wants when an integration and a workspace disagree about what happened.

The API side keeps the key, the payload hash, the status and the response for a day. It is a shorter memory for a different purpose: enabling a retry rather than documenting a relationship.

Five questions to ask about request deduplication

What happens with no key?

A good answer sounds like

It depends on the caller — say which.

What ours actually is

On the public interface, nothing is deduplicated. On an inbound integration, a key is synthesised from the credential and the body.

How long does a key last?

A good answer sounds like

A stated window.

What ours actually is

Twenty-four hours on the interface. No expiry on the integration path, deliberately.

What does a retry receive?

A good answer sounds like

The original response.

What ours actually is

The stored body and the stored status code, so the client sees the identifiers the first attempt produced.

What if a key is reused for different content?

A good answer sounds like

A refusal.

What ours actually is

A conflict response with a named error code, rather than processing either version.

Can two real events be mistaken for a duplicate?

A good answer sounds like

An honest yes, with the remedy.

What ours actually is

On the integration path, yes, if the bodies are byte-identical. The remedy is an event identifier or an explicit key in the request.

Scope, not a ceiling

Around the two policies

Both mechanisms are correct for where they sit. What integrators ask for is visibility into what was deduplicated and a way to say when identical really means distinct.

A replay report

Which inbound requests were treated as replays and when, so an integration seeing unexpected results can be diagnosed from your side.

A configurable window per credential

An expiry on the integration path for callers whose events legitimately repeat, set per integration rather than globally.

A declared event identifier

A documented field an integration can use to say "this is a distinct occurrence", rather than having to vary the body to be believed.

We publish scope, not dates.

Scope an integration

The deduplication ledger, precisely

What AWRA OpsHub does today

  • Opt-in idempotency on the public interface, keyed on a standard header or a field in the request, scoped to the organisation.
  • A replay that returns the stored response body and status code rather than a fresh acknowledgement, so a retry receives the identifiers the original attempt produced.
  • A conflict response with a named error code when a key is reused with different content, rather than processing either version.
  • A twenty-four hour window on the interface, after which a key is treated as new.
  • Automatic replay protection on inbound integration calls, with a key synthesised from the credential and the exact request body when none is supplied.
  • Two accepted header names on the integration path, one standard and one product-specific.
  • A per-request log on the integration path recording the credential, the key, a hash of the body, the source address, and the eventual status, response and completion time.
  • No expiry on the integration path, so a repeated body is recognised however long afterwards it arrives.

More we can add to your workspace

  • A report of what was treated as a replay, so an integration seeing unexpected results can be diagnosed without reading logs.
  • A configurable expiry per integration credential, for callers whose events legitimately repeat with identical content.
  • A documented event identifier field, letting an integration declare a distinct occurrence rather than having to vary its body.
  • Idempotency on the interface applied by default to the operations where a duplicate would be most costly.
  • A notification when a conflict is returned, since a reused key with different content usually means a client bug worth telling somebody about.
  • A view of an integration's recent requests in the workspace, rather than only in the log.

Where we point you to a specialist

  • We will not deduplicate identical requests on the public interface by default. Two identical adjustments an hour apart are two adjustments, and a system that silently discarded the second would be wrong in a way its caller could not detect.
  • We will not process an inbound integration request twice because it carried no key. A machine posting the same body twice is retrying far more often than it is reporting a coincidence, and the cost of a duplicate stock movement is higher than the cost of an integration having to vary its payload.
  • How an integration distinguishes two genuinely identical events is a contract question we will document rather than guess at. Inventing a distinction from timing or arrival order would make the behaviour depend on network conditions, which is the least predictable input available.

A replay report is the contained piece here, and it answers the only question this design reliably produces: why did the other side think it sent something we did not receive.

Decide which side of the line your integration is on

If it can post the same body for two real events, it needs to vary them or supply its own key. That is a five-minute decision at design time and a difficult investigation later.

Talk through integrations

Frequently asked questions

Why does the integration path deduplicate without a key and the interface not?

Because the two callers differ. An integration posting an identical body is nearly always retrying after a timeout, so treating it as a replay is the safe default. A developer calling the interface may legitimately create two identical records an hour apart, and silently discarding the second would be a defect the caller could not detect. The two safe defaults point in opposite directions, which is why there are two mechanisms.

What does a retry actually receive?

The original response — the stored body with the stored status code — rather than a fresh success. That distinction is the whole value: the caller retried because it never saw the first response, and the first response contained the identifier of whatever was created. Replaying it means the client ends up in the same state whether or not the original ever arrived.

Our integration sends the same body for two real events. What do we do?

Make the requests differ, or supply an explicit key per event. A timestamp, an event identifier or a source reference in the body is enough. Two byte-identical requests are indistinguishable from a retry, and the contract on that path is that identical content means the same occurrence.

How long does an idempotency key last?

Twenty-four hours on the public interface — long enough for any plausible retry and short enough that a key reused the following week is treated as new. On the inbound integration path there is deliberately no expiry, so a repeated body is recognised however long afterwards it arrives.

What happens if we reuse a key with different content?

The interface refuses with a conflict status and a named error code rather than processing either version. That case is a client reusing a key for a different operation, and processing it would produce an outcome neither side intended — a refusal is the only answer that leaves the caller able to correct itself.

Can we see what was treated as a duplicate?

Every inbound integration request is logged with its credential, key, body hash, source address and eventual response, so the information exists. Surfacing it as a report is the first item on the list of what would be added, and it is what turns "the other side says it sent it" into a question with an answer.

Help Center

Need a quick answer while you read?

Run inventory, procurement, assets, sales, and field work with approved AWRA guidance for setup, migration, integrations, security, pricing, and support.

Search all approved AWRA public help articles.

Open Help Center