Configuration

All configuration can be set via the published config file or environment variables.

Header Names

Idempotency Key Header

The header clients use to send their idempotency key:

'main_header_name' => env('IDEMPOTENCY_MAIN_HEADER', 'Idempotency-Key'),

Replayed Response Header

The header added to responses that are served from cache:

'repeated_header_name' => env('IDEMPOTENCY_REPEATED_HEADER', 'Idempotent-Replayed'),

Expiration Time

How long cached responses are stored, in minutes. After this time, the idempotency key can be reused:

'expiration_time' => env('IDEMPOTENCY_EXPIRATION_TIME', 360), // 6 hours

HTTP Methods

Which HTTP methods the middleware applies to. Requests using other methods are passed through without idempotency handling:

'http_methods' => ['POST', 'PUT', 'PATCH', 'DELETE'],

To limit it to only POST requests:

'http_methods' => ['POST'],

Environment Variables

You can configure everything via .env without publishing the config file:

IDEMPOTENCY_MAIN_HEADER=Idempotency-Key
IDEMPOTENCY_REPEATED_HEADER=Idempotent-Replayed
IDEMPOTENCY_EXPIRATION_TIME=360

Scoping the Cache (multi-tenant apps)

By default the middleware stores each response under the client's idempotency key alone, a single global namespace. In a multi-tenant API that means two tenants (or the same tenant in live vs. test) that happen to send the same key would collide.

To prevent this, bind your own IdempotencyScope. The middleware asks it for a scope string and partitions the cache by it, so the same key is isolated per scope:

use BlueBeetle\IdempotencyMiddleware\Contracts\IdempotencyScope;
use Illuminate\Http\Request;

final class TenantIdempotencyScope implements IdempotencyScope
{
    public function resolve(Request $request): string
    {
        // Return a stable identifier for the authenticated tenant/environment.
        // The middleware runs after your auth middleware, so the user is available.
        $user = $request->user();

        return $user === null ? '' : "{$user->tenant_id}:{$user->environment}";
    }
}

Bind it in a service provider:

$this->app->bind(IdempotencyScope::class, TenantIdempotencyScope::class);

Return an empty string for the global scope. If you don't bind a resolver, the default (GlobalIdempotencyScope) keeps the original single-namespace behaviour.

Cache Driver

The middleware uses Laravel's default cache driver. To use a specific driver for idempotency, configure a dedicated cache store in config/cache.php and set it as default, or use a tagged cache approach in your application.

For production, a persistent cache driver like Redis or Memcached is recommended to ensure idempotency keys survive across deployments.