Skip to main content

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
tip

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,
],
StrategyBehavior
exceptionSecond request immediately throws ConcurrentInvocationException (409)
waitSecond request waits for the first to complete, then returns cached response

See Stores for details on how locking is implemented per store.