adilazhari / laravel-idempotency
A Laravel package providing idempotent request handling.
Requires
- php: ^8.5.0
- illuminate/cache: ^13.0
- illuminate/config: ^13.0
- illuminate/contracts: ^13.0
- illuminate/http: ^13.0
- illuminate/support: ^13.0
Requires (Dev)
- larastan/larastan: ^3.10
- laravel/framework: ^13.20
- laravel/pint: ^1.29.3
- orchestra/testbench: ^11.1
- pestphp/pest: ^5.0.0
- pestphp/pest-plugin-phpstan: ^5.0.0
- pestphp/pest-plugin-rector: ^5.0.0
- pestphp/pest-plugin-type-coverage: ^5.0.0
- phpstan/phpstan: ^2.2.5
- rector/rector: ^2.5.7
- symfony/var-dumper: ^8.1.1
README
Laravel Idempotency
Framework-native HTTP idempotency for Laravel applications.
Why Another Idempotency Package?
Laravel applications commonly implement idempotency in an ad-hoc manner, often coupling request replay, persistence, and locking into application code.
Laravel Idempotency provides a reusable, framework-native solution built around explicit contracts and interchangeable components, allowing storage, fingerprinting, and locking strategies to evolve independently.
Laravel Idempotency
Laravel Idempotency provides a framework-native solution for ensuring that identical HTTP requests are executed exactly once.
Using an Idempotency-Key, the package stores the original HTTP response and automatically replays it for subsequent identical requests, eliminating accidental duplicate operations caused by retries or network failures.
It is particularly useful for:
- Payment processing
- Subscription management
- Order creation
- Inventory updates
- Account changes
- External API integrations
- Webhook processing
Why Idempotency?
Distributed systems are unreliable.
Clients retry requests.
Browsers retry requests.
Mobile applications automatically retry after network interruptions.
Load balancers and reverse proxies may resend requests.
Without idempotency, a single retry can unintentionally create duplicate:
- Payments
- Orders
- Invoices
- Subscriptions
- Shipments
- Customer accounts
Laravel Idempotency guarantees that the same logical operation executes only once while returning the original response for every identical retry.
Features
- HTTP idempotency middleware
- Atomic request locking
- Automatic response replay
- SHA-256 request fingerprinting
- Configurable response expiration
- Multiple storage drivers
- Array
- Cache
- Redis
- Database
- Database pruning command
- Custom fingerprint strategies
- Custom storage implementations
- Laravel-native dependency injection
- Fully tested
- Production-ready
Requirements
- PHP 8.5+
- Laravel 13+
For production deployments, use a shared storage backend such as Redis or the Database driver.
Installation
Install the package using Composer.
composer require adilazhari/laravel-idempotency
Laravel automatically discovers the package.
Publish the configuration file:
php artisan vendor:publish --tag=idempotency-config
If you intend to use the database storage driver, also publish the migration:
php artisan vendor:publish --tag=idempotency-migrations php artisan migrate
Quick Start
Protect any write endpoint using the middleware.
use AdilAzhari\LaravelIdempotency\Http\Middleware\IdempotencyMiddleware; use Illuminate\Support\Facades\Route; Route::post('/payments', CreatePaymentController::class) ->middleware(IdempotencyMiddleware::class);
Clients simply provide an idempotency key.
POST /payments Idempotency-Key: payment-123 { "amount": 1000, "currency": "MYR" }
The first request executes normally.
Subsequent identical requests return the previously stored response without executing the controller again.
How It Works
Client
│
▼
Idempotency Middleware
│
▼
Generate Request Fingerprint
│
▼
Acquire Execution Lock
│
┌───────────┴───────────┐
│ │
▼ ▼
Existing Response? No Response
│ │
Yes ▼
│ Execute Controller
│ │
▼ ▼
Replay Stored Response Store HTTP Response
│ │
└───────────┬───────────┘
▼
Release Lock
│
▼
Return Response
Request Matching
Two requests are considered identical when all of the following match:
- Idempotency key
- HTTP method
- Request path
- Query parameters
- Parsed request body
The default implementation uses the built-in Sha256RequestFingerprinter.
For example:
POST /payments { "amount":1000 }
and
POST /payments { "amount":2000 }
are treated as different requests, even if they share the same idempotency key.
If the same key is reused for different request data, an IdempotencyConflictException is thrown.
Storage Drivers
Laravel Idempotency supports multiple persistence backends.
| Driver | Recommended Usage |
|---|---|
| Array | Testing |
| Cache | Default |
| Redis | Distributed applications |
| Database | Durable persistence |
Select the active driver through configuration.
IDEMPOTENCY_STORE=cache
Switching drivers requires no application code changes.
Configuration
return [ 'driver' => env('IDEMPOTENCY_STORE', 'cache'), 'stores' => [ 'cache' => [ 'driver' => 'cache', ], 'array' => [ 'driver' => 'array', ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'prefix' => 'idempotency:', ], 'database' => [ 'driver' => 'database', ], ], 'header' => 'Idempotency-Key', 'lock' => [ 'seconds' => 10, ], 'expiration' => 86400, ];
| Option | Description |
|---|---|
driver |
Active storage driver |
header |
HTTP header containing the idempotency key |
lock.seconds |
Maximum lock duration |
expiration |
Lifetime of stored responses |
The lock duration should comfortably exceed the execution time of your protected endpoints.
Extending
Laravel Idempotency is built around a small set of contracts, allowing individual components to be replaced without modifying the package.
Custom Request Fingerprinting
Bind your own implementation of the RequestFingerprinter contract.
use AdilAzhari\LaravelIdempotency\Contracts\RequestFingerprinter; $this->app->bind( RequestFingerprinter::class, CustomRequestFingerprinter::class, );
Possible use cases include:
- Ignoring selected request fields
- Including additional HTTP headers
- Supporting custom hashing algorithms
- Multi-tenant request isolation
Custom Storage
Implement the IdempotencyStore contract to persist responses anywhere.
use AdilAzhari\LaravelIdempotency\Contracts\IdempotencyStore; $this->app->bind( IdempotencyStore::class, CustomIdempotencyStore::class, );
Possible implementations include:
- Redis
- Database
- DynamoDB
- MongoDB
- Amazon S3
- External persistence services
Custom Locking
Locking is also replaceable.
use AdilAzhari\LaravelIdempotency\Contracts\IdempotencyLock; $this->app->bind( IdempotencyLock::class, CustomIdempotencyLock::class, );
This allows integration with:
- Redis locks
- Database locks
- Distributed lock services
- Cloud-native coordination systems
Storage Drivers
Cache Driver
The Cache driver is the default storage implementation.
IDEMPOTENCY_STORE=cache
It stores responses using Laravel's configured cache store and is suitable for most applications.
Array Driver
The Array driver stores responses in memory.
IDEMPOTENCY_STORE=array
This driver is intended for:
- Unit tests
- Local development
- Temporary storage
Because data exists only in memory, it should not be used in production.
Redis Driver
The Redis driver stores responses directly in Redis.
IDEMPOTENCY_STORE=redis
Configuration:
'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'prefix' => 'idempotency:', ],
This driver is recommended for distributed deployments where multiple application instances must share idempotency records.
Database Driver
The Database driver persists responses using Eloquent.
IDEMPOTENCY_STORE=database
Before using it, publish the migration:
php artisan vendor:publish --tag=idempotency-migrations php artisan migrate
The database driver provides durable storage that survives cache flushes and application restarts.
Pruning Expired Records
Expired database records can be removed using the built-in Artisan command.
php artisan idempotency:prune
Scheduling the command is recommended.
use Illuminate\Support\Facades\Schedule; Schedule::command('idempotency:prune') ->daily();
Design Principles
Laravel Idempotency is intentionally built around a small set of principles.
- Framework-native integration
- Explicit contracts
- Predictable behaviour
- Simple public API
- Testability
- Extensibility
Testing
Run the complete test suite.
composer test
The package includes comprehensive unit and feature tests covering:
- Request replay
- Conflict detection
- Request fingerprinting
- Atomic locking
- Cache storage
- Redis storage
- Database storage
- Array storage
- Middleware integration
- Service provider registration
- Configuration
- Console commands
Documentation
Additional documentation is available in the docs directory.
- Architecture
- Storage Drivers
- Custom Fingerprinting
- Custom Storage Drivers
- Custom Locking
- Contributing Guide
Roadmap
- ✅ Laravel 13 support
- ✅ HTTP idempotency middleware
- ✅ Atomic request locking
- ✅ SHA-256 request fingerprinting
- ✅ Cache storage driver
- ✅ Array storage driver
- ✅ Redis storage driver
- ✅ Database storage driver
- ✅ Configurable storage drivers
- ✅ Database pruning command
- 🚧 Additional storage adapters
Contributing
Contributions are welcome.
If you discover a bug or would like to propose a new feature, please open an issue before submitting a pull request so the implementation can be discussed first.
When contributing:
- Follow the existing coding style.
- Include tests for new functionality.
- Ensure
composer testpasses before opening a pull request.
License
Laravel Idempotency is open-source software licensed under the MIT License.

