ademakanaky / laravel-enterprise-idempotency
Enterprise-grade, concurrency-safe idempotency middleware for Laravel APIs.
Package info
github.com/ademakanaky/laravel-idempotency
pkg:composer/ademakanaky/laravel-enterprise-idempotency
Requires
- php: ^8.3
- illuminate/cache: ^10.0|^11.0
- illuminate/database: ^10.0|^11.0
- illuminate/http: ^10.0|^11.0
- illuminate/support: ^10.0|^11.0
Requires (Dev)
- orchestra/testbench: ^8.0
- phpunit/phpunit: ^9.6
README
Enterprise-grade Idempotency Middleware for Laravel APIs
This package prevents duplicate request processing and safely
replays the original response when a client retries a request using
the same Idempotency-Key.
It is designed for financial systems, payment APIs, fintech platforms, loan services, and mission‑critical backend systems where duplicate processing must be prevented.
Table of Contents
- Features
- Installation
- Basic Usage
- How Idempotency Works
- Drivers
- Configuration
- Request Hash Protection
- Automatic User Scoping
- Response Replay
- Example Payment Flow
- Testing
- Package Structure
- Security Considerations
- Example Idempotency Key Generation
- Contributing
- License
Features
- Multiple idempotency drivers
- Cache driver
- Database driver
- Hybrid driver (Database + Cache Lock)
- Automatic response replay
- Request hash validation
- Per-user idempotency scope
- Configurable response status codes
- Distributed locking support
- Replay count tracking
- Pluggable driver architecture
- Fully tested (Unit + Feature tests)
Installation
Install the package via Composer:
composer require ademakanaky/laravel-idempotency
Publish the configuration:
php artisan vendor:publish --tag=idempotency-config
Publish the migration:
php artisan vendor:publish --tag=idempotency-migrations
Run migrations:
php artisan migrate
Basic Usage
Apply the middleware to routes that require idempotency protection.
Route::post('/payments', PaymentController::class) ->middleware('idempotency');
Clients must include an Idempotency-Key header.
Example request:
POST /payments
Idempotency-Key: PAY-123456789
How Idempotency Works
First Request
Client sends request:
Idempotency-Key: abc123
- The request is processed normally.
- The response is stored.
- The idempotency key is recorded.
Retry Request
Client retries using the same key:
Idempotency-Key: abc123
Instead of executing the request again:
- The stored response is returned.
- The request handler is not executed again.
Response header:
Idempotency-Replayed: true
Drivers
Driver configuration is located at:
config/idempotency.php
Cache Driver
Uses Laravel cache to store responses.
Best for:
- Short‑lived APIs
- Lightweight operations
<!-- -->
driver = cache
Database Driver
Stores request and response data in the database.
Best for:
- Financial systems
- Audit trails
- Compliance environments
<!-- -->
driver = database
Hybrid Driver (Recommended)
Uses:
- Cache locking
- Database persistence
Benefits:
- Prevents concurrent duplicate processing
- Ensures durable response replay
<!-- -->
driver = hybrid
Configuration
Example configuration file:
return [ 'driver' => env('IDEMPOTENCY_DRIVER', 'hybrid'), 'ttl_minutes' => 60, 'status_codes' => [ 200, 201, 202, ], 'lock' => [ 'enabled' => true, 'seconds' => 10, ], 'idempotency_model' => \Ademakanaky\EnterpriseIdempotency\Models\IdempotencyRecord::class, ];
Request Hash Protection
Requests are hashed using:
HTTP Method + Route + Request Body
If the same Idempotency-Key is reused with different request data,
the request will fail.
Example response:
409 Conflict
Idempotency key conflict
This ensures request integrity.
Automatic User Scoping
Idempotency keys are automatically scoped to avoid collisions.
Priority:
- Authenticated User ID
- Client IP Address
This prevents two different users from accidentally sharing the same idempotency key.
Response Replay
When a stored response is returned, the API includes the header:
Idempotency-Replayed: true
This allows API clients to detect that a response came from a retry.
Example Payment Flow
Initial Request
POST /payments
Idempotency-Key: PAY-001
A network failure occurs before the client receives the response.
Client Retry
POST /payments
Idempotency-Key: PAY-001
Instead of creating a second payment, the API returns the original stored response.
Architecture Overview
Client
|
| POST /payments
| Idempotency-Key
v
Idempotency Middleware
|
|-- Check existing key
|-- Validate request hash
|-- Acquire lock (Hybrid)
|
v
Application Controller
|
v
Response Stored
|
v
Future retries replay stored response
Testing
Run the full test suite:
vendor/bin/phpunit
Test coverage includes:
- Unit Tests
- Feature Tests
- Driver Tests
- Middleware Concurrency Tests
Package Structure
laravel-idempotency
│
├── src
│ ├── Contracts
│ │ └── IdempotencyDriver.php
│ │
│ ├── Drivers
│ │ ├── CacheDriver.php
│ │ ├── DatabaseDriver.php
│ │ └── HybridDriver.php
│ │
│ ├── Http
│ │ └── Middleware
│ │ └── IdempotencyMiddleware.php
│ │
│ ├── Models
│ │ └── IdempotencyRecord.php
│ │
│ ├── Providers
│ │ └── IdempotencyServiceProvider.php
│ │
│ └── Services
│ └── IdempotencyManager.php
│
├── config
│ └── idempotency.php
│
├── database
│ └── migrations
│
├── tests
│ ├── Feature
│ └── Unit
│
├── composer.json
└── phpunit.xml
Security Considerations
This package ensures:
- Duplicate requests are not processed twice
- Safe retry behavior
- Request integrity validation
- Replay protection
Recommended for:
- Payment APIs
- Loan systems
- Order processing
- Financial microservices
- Distributed systems
Example Idempotency Key Generation
Clients should generate unique idempotency keys.
Example in Laravel:
use Illuminate\Support\Str; $key = 'PAY-' . Str::uuid();
Example request header:
Idempotency-Key: PAY-550e8400-e29b-41d4-a716-446655440000
Contributing
Pull requests are welcome.
Please ensure all tests pass before submitting:
vendor/bin/phpunit
TO-DO:
- Add Idempotency Expiration Cleanup Command – this is important so the idempotency_keys table does not grow indefinitely.
- Still thinking about what else to add...
License
MIT License