-
Notifications
You must be signed in to change notification settings - Fork 2
Idempotency
Viames Marino edited this page Feb 23, 2026
·
2 revisions
Pair\Api\Idempotency prevents duplicate execution of mutating endpoints.
Implementation details:
- file-based storage under
TEMP_PATH/idempotency - key scoped by custom scope string + client idempotency key
- request hash check (
method + URI + body) - replay of previously stored response
Client can send one of:
Idempotency-KeyX-Idempotency-Key
use Pair\Api\Idempotency;
use Pair\Api\ApiResponse;
Idempotency::respondIfDuplicate($this->request, 'orders:create');
$result = ['orderId' => 123, 'saved' => true];
Idempotency::storeResponse($this->request, 'orders:create', $result, 201);
ApiResponse::respond($result, 201);Behavior:
- no key -> returns
trueand request continues - existing key with same hash and completed response -> returns cached response immediately
- existing key with same hash and status
processing->CONFLICT - existing key with different request hash ->
CONFLICT - new key -> writes
processinglock
storeResponse(Request $request, string $scope, mixed $data, int $httpCode = 200, int $ttlSeconds = 86400): bool
Stores canonical response for future retries with same key.
Clears lock file, useful in explicit rollback/error flows.
- Use stable scope names (
orders:create,billing:payInvoice). - Always call
storeResponse(...)before sending final success response. - Keep TTL aligned with retry windows used by clients/offline queues.
use Pair\Api\Idempotency;
use Pair\Api\ApiResponse;
Idempotency::respondIfDuplicate($this->request, 'orders:create', 86400);
try {
$payload = $this->request->validate([
'customerId' => 'required|int',
'amount' => 'required|numeric|min:0.01',
]);
$result = [
'orderId' => 2241,
'customerId' => (int)$payload['customerId'],
];
Idempotency::storeResponse($this->request, 'orders:create', $result, 201, 86400);
ApiResponse::respond($result, 201);
} catch (\Throwable $e) {
Idempotency::clearProcessing($this->request, 'orders:create');
throw $e;
}If the same idempotency key is reused with a different body/URI/method hash, Pair returns CONFLICT. This protects against accidental key reuse bugs in clients.
// no idempotency header -> method returns true and request continues normally
Idempotency::respondIfDuplicate($this->request, 'payments:create');- Forgetting
clearProcessing()in exception paths. - Using generic scopes that collide across operations.
- Storing huge response payloads unnecessarily in idempotency files.
See also: API, Request, ApiResponse, PWA.