HTTP Middleware
truschery/idem provides an HTTP middleware that intercepts incoming requests and guarantees idempotent behavior — if a request with the same key has already been processed, the cached response is returned immediately without re-executing the handler.
Registering the Middleware
The middleware is available under the alias defined in config (idempotent by default). Apply it to any route or group:
// Single route
Route::post('/orders', OrderController::class)->middleware('idempotent');
// Group
Route::middleware('idempotent')->group(function () {
Route::post('/orders', OrderController::class);
Route::patch('/orders/{id}', UpdateOrderController::class);
});
Idempotency-Key Header
The client is responsible for generating and sending the key with each request:
POST /orders HTTP/1.1
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Use UUID v4 generated client-side per request. Never reuse keys across different operations.
If the Idempotency-Key header is not present, idempotency is not applied — the request passes through as normal.
Supported Methods
By default, truschery/idem applies idempotency to POST and PATCH requests. You can change this in the config:
// config/idempotency.php
'request' => [
'idempotent_methods' => ['POST', 'PATCH', 'PUT'],
],
GET requests are already idempotent by nature and are not supported.
Replay
When a repeated request is detected and the cached response is returned, the response will contain the Idempotency-Relay header:
HTTP/1.1 200 OK
Idempotency-Relay: true
Only 2xx responses are cached. If the original request returned an error, the next request will be processed again.
Hash Protection
Each request is fingerprinted based on its path and parameters. If the same Idempotency-Key is reused with a different payload, truschery/idem will throw:
Truschery\Idem\Exceptions\IdempotencyHashMismatchException
This protects against accidental key reuse across different operations.
Concurrent Requests
If two requests arrive simultaneously with the same key, the behavior depends on the strategy set in config:
// config/idempotency.php
'lock_wait' => [
'strategy' => 'exception', // or 'wait'
'timeout' => 10,
],
| Strategy | Behavior |
|---|---|
exception | Second request immediately throws ConcurrentInvocationException (409) |
wait | Second request waits for the first to complete, then returns cached response |
See Stores for details on how locking is implemented per store.