Idempotency
Safely retry write operations across network failures by attaching a unique Idempotency-Key header to write requests (POST, PUT, DELETE, PATCH). When enabled for your instance, the API returns the original response on retry without re-executing the operation.
Opt-in per instance. If 7G has not enrolled your instance, the header is silently ignored and API behaviour is unchanged. Once enrolled, the header is required on every write request (POST, PUT, DELETE, PATCH).
HTTP/REST and .NET SDK consumers both attach the same Idempotency-Key header. The SDK does not generate keys automatically - see Integration Patterns below.
Quick Reference
Header
Name: Idempotency-Key
Format: up to 255 characters
Recommended: UUID v4
Generation: caller-side, one key per logical operation
Scope
Methods: POST, PUT, DELETE, PATCH
Excluded paths: /user/login, /common/lookup, /common/HealthCheck
Isolation: keys are scoped to your 7G instance
Replay Window
TTL: 24 hours
Cached: 2xx responses only
Replay signal: Idempotent-Replayed: true response header
Why This Matters
Network failures and load-balancer retries can leave a write in an uncertain state - the request may or may not have reached the server. Idempotency lets you retry write requests safely, returning the original response on retry instead of executing the operation twice.
When This Applies To You
Two behaviours, one per instance status
| Instance Status | With Idempotency-Key |
Without Idempotency-Key |
|---|---|---|
| Not enrolled (default) | Header is silently ignored. No replay protection. | Unchanged behaviour. |
| Enrolled | Replay protection active for 24 hours. | HTTP 400. The header is mandatory. |
If 7G has not explicitly confirmed enrolment for your instance, you are not enrolled.
How It Works
Three observable outcomes once enrolled
First request with a new key
The API processes the request normally. On a 2xx response, the status, body, and a SHA-256 hash of method + path + query string + body are stored under your instance ID and the supplied key for 24 hours. Non-2xx responses are not stored.
Concurrent requests with the same key - whether from multiple threads or multiple application pods - are serialised by the API. One executes; the others wait and receive the cached response, so you do not need client-side coordination.
Retry with the same key and same payload
The API returns the original cached response verbatim - same status, body, and content type - without re-running the operation. The response carries one additional header:
Idempotent-Replayed: trueSame key, different payload
If method, path, query string, or body hash differ from the original, the API rejects the request:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"result": false,
"message": "Idempotency key was reused with a different request payload or endpoint.",
"data": {}
}Generate a new key for the new operation.
Request Format
Header rules, covered methods, and matching algorithm
The header name is case-insensitive (per HTTP spec). The server treats the value as an opaque string and does not interpret its contents - only equality matters. Refer to the Quick Reference for length and format.
Methods Covered
Idempotency applies to all four write methods: POST, PUT, DELETE, and PATCH. POST, PUT, and DELETE are in active use across the 7G API today; PATCH is reserved for future endpoints - if one is added, enrolled instances will need to attach the header to PATCH calls too.
Excluded Paths
The following endpoints are always exempt, regardless of enrolment:
POST /user/loginGET /common/lookupGET /common/HealthCheck
Note: POST /User/RefreshToken is not excluded. Enrolled instances must attach the header to refresh calls too - if you rely on the SDK's automatic token refresh, use the DelegatingHandler pattern shown in Integration Patterns below.
Matching Algorithm
Two requests are considered identical when all four of the following match:
- HTTP method
- Request path
- Query string
- SHA-256 hash of the request body
Any difference while the key is reused triggers the 422 response described above.
Large bodies: request bodies above 1 MB are hashed by length only rather than by content. Two same-length oversized payloads sent under one key would be treated as identical - use distinct keys for large uploads (e.g. POST /Document/File) to avoid an accidental replay.
curl -X POST 'https://test-api.7g.com.au/BizTransaction/Deposit' \
-H 'Authorization: eyJxxx_your_access_token_here' \
-H 'Version: 2.0' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 9f8c0e2a-1b3d-4c5f-8e7a-2d4b6f0a1c3e' \
-d '{
"accountID": 12345,
"investmentID": 25,
"amount": 50000.00,
"paymentMethodID": 1,
"transactionDate": "2026-05-13"
}'Response Signals
Distinguishing executed requests from replays
Replayed responses carry one additional header. It is absent on first-time responses.
Idempotent-Replayed: trueWhat Gets Cached
| Response Status | Cached? | Retry Semantics |
|---|---|---|
| 2xx | Yes - 24 hours | Same key returns the cached response without re-execution. |
| 4xx | No | Fix the underlying issue and retry with the same key. |
| 5xx | No | Retry with the same key. The next attempt executes for real. |
Because only successful responses are cached, a failed write is always safe to retry with the same key.
HTTP/1.1 200 OK
Content-Type: application/json
{
"result": true,
"message": "Deposit processed successfully",
"recordCount": 1,
"data": {
"bizTransactionID": 3001,
"externalBizTransactionId": "DEP-2026-001",
"accountID": 12345,
"investmentID": 25,
"amount": 50000.00,
"quantity": 40000.00,
"transactionDate": "2026-05-13T00:00:00Z",
"statusName": "Complete"
}
}Error Responses
Exact messages returned by the middleware
| Status | Scenario | Message | Resolution |
|---|---|---|---|
400 |
Header missing (enrolled instance) | "'Idempotency-Key' header is required for POST requests." (or PUT, DELETE, PATCH) | Include the header. |
400 |
Key exceeds 255 characters | "'Idempotency-Key' must be 255 characters or fewer." | Shorten the key. A UUID v4 is 36 characters. |
422 |
Key reused with different payload | "Idempotency key was reused with a different request payload or endpoint." | Generate a fresh key. |
Errors use the standard APIResponse envelope. See Responses & Error Handling for the full pattern.
Integration Patterns
Attaching keys from HTTP/REST and from the .NET SDK
HTTP/REST
Generate a UUID per logical operation, send it as a header, and persist it locally so retries reuse the same value.
#!/bin/bash
KEY=$(uuidgen)
for attempt in 1 2 3; do
STATUS=$(curl -sS -o response.json -w '%{http_code}' \
-X POST 'https://test-api.7g.com.au/BizTransaction/Deposit' \
-H "Authorization: $TOKEN" \
-H 'Version: 2.0' \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $KEY" \
-d @payload.json)
[ "$STATUS" -ge 200 ] && [ "$STATUS" -lt 300 ] && break
[ "$STATUS" -ge 400 ] && [ "$STATUS" -lt 500 ] && break
sleep $((attempt * 2))
done.NET SDK
The SDK does not attach idempotency keys automatically, and SevenGClient does not expose its inner HttpClient. Construct your own HttpClient and pass it to the SevenGClient(HttpClient) constructor - the parameterless SevenGClient(SevenGEnvironment) overload owns its HttpClient internally and gives no header control.
- DelegatingHandler (recommended) - attaches a key to every write request automatically.
- Caller-supplied key - required when retrying a logical operation, so the same key is preserved across attempts.
- Per-call header - simplest, but easy to leave a stale key on
DefaultRequestHeaders.
Automatic token refresh needs a key too
The SDK's EnsureValidTokenAsync issues POST /User/RefreshToken silently when the access token nears expiry. That endpoint is not on the excluded list, so enrolled instances must cover it. The DelegatingHandler pattern handles refresh transparently; the Caller-supplied and Per-call patterns do not - if the SDK refreshes between your business calls, the refresh request will hit HTTP 400. Prefer the handler if your session may outlive a single access token.
public class IdempotencyHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.Method == HttpMethod.Post ||
request.Method == HttpMethod.Put ||
request.Method == HttpMethod.Delete ||
request.Method == HttpMethod.Patch)
{
// Preserve caller-set keys (used by retry paths below)
if (!request.Headers.Contains("Idempotency-Key"))
{
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
}
}
return base.SendAsync(request, cancellationToken);
}
}
// Hand the SDK an HttpClient with the handler attached
var httpClient = new HttpClient(new IdempotencyHandler
{
InnerHandler = new HttpClientHandler()
})
{
BaseAddress = new Uri("https://test-api.7g.com.au")
};
var client = new SevenGClient(httpClient);
await client.Auth.LoginAsync("your-username", "your-password");
// Every write request now carries an Idempotency-Key
var response = await client.BizTransaction.DepositAsync(depositDto);Best Practices
Guidelines for predictable retry behaviour
Recommended
- Use UUID v4 for keys
- Generate one key per logical operation, not per HTTP attempt - retries of the same operation must reuse the same key
- Persist the key locally before sending, so a crash mid-flight can resume with the same value
- Log the key alongside your request ID for end-to-end traceability
- Retry non-2xx responses with the same key - nothing was cached, so the next attempt executes for real
- Treat HTTP 422 with the reuse message as a code defect, not a transient failure
- Monitor the
Idempotent-Replayed: trueresponse header to track retry behaviour
Avoid
- Reuse the same key across different operations - this returns HTTP 422
- Generate a new key on every retry attempt - this defeats deduplication
- Use sequence numbers, timestamps, or business IDs as keys - they invite collisions
- Send the header on GET requests - the server ignores it, but it clutters your own client traces
- Cache responses client-side based on the key - the server already does this