PHPackages                             grazulex/laravel-api-idempotency - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. grazulex/laravel-api-idempotency

ActiveLibrary[API Development](/categories/api)

grazulex/laravel-api-idempotency
================================

RFC-compliant idempotency support for Laravel APIs - Prevent duplicate operations, ensure safe retries

v1.0.0(3mo ago)9283↓33.3%1MITPHPPHP ^8.3CI passing

Since Feb 4Pushed 3mo agoCompare

[ Source](https://github.com/Grazulex/laravel-api-idempotency)[ Packagist](https://packagist.org/packages/grazulex/laravel-api-idempotency)[ Docs](https://github.com/grazulex/laravel-api-idempotency)[ Fund](https://paypal.me/strauven)[ GitHub Sponsors](https://github.com/Grazulex)[ RSS](/packages/grazulex-laravel-api-idempotency/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (12)Versions (2)Used By (0)

Laravel API Idempotency
=======================

[](#laravel-api-idempotency)

> Complete API idempotency lifecycle management for Laravel - Prevent duplicate operations, ensure safe retries

[![Latest Version on Packagist](https://camo.githubusercontent.com/0d2c656698fb727e9f02f28668b9d9a513a7938bc0c96c298e721601d46b0829/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6772617a756c65782f6c61726176656c2d6170692d6964656d706f74656e63792e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/grazulex/laravel-api-idempotency)[![Tests](https://camo.githubusercontent.com/c059e192fad47627121d87dd082d44d92aadd3965047ebe691665749b8461680/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6772617a756c65782f6c61726176656c2d6170692d6964656d706f74656e63792f74657374732e796d6c3f6c6162656c3d7465737473)](https://github.com/grazulex/laravel-api-idempotency/actions)[![Total Downloads](https://camo.githubusercontent.com/c697d0bdf139fd3ac6178a15ccaf08be874f2a99c07f68442e03e1d9363a1957/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6772617a756c65782f6c61726176656c2d6170692d6964656d706f74656e63792e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/grazulex/laravel-api-idempotency)[![License](https://camo.githubusercontent.com/29c58022e396221dae05a096294f154bcebb34298e785fe2821c4a40f9fc6361/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6772617a756c65782f6c61726176656c2d6170692d6964656d706f74656e63792e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/grazulex/laravel-api-idempotency)

---

Features
--------

[](#features)

- **RFC Draft Compliant** - Follows [IETF Idempotency-Key Header Draft](https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/)
- **Multiple Storage Drivers** - Cache, Redis, Database, or DynamoDB
- **Payload Fingerprinting** - SHA256 verification prevents key reuse with different data
- **Conflict Detection** - Handles concurrent requests with wait/reject strategies
- **Scoping** - Scope keys to user, tenant, IP, or custom resolver
- **Zero Configuration** - Works out of the box with sensible defaults
- **Artisan Commands** - Stats, cleanup, and management tools
- **Testing Helpers** - Fluent testing API with `IdempotencyFake`

---

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 11.x or 12.x

---

Installation
------------

[](#installation)

```
composer require grazulex/laravel-api-idempotency
```

Publish the configuration:

```
php artisan vendor:publish --tag="api-idempotency-config"
```

For database driver, publish migrations:

```
php artisan vendor:publish --tag="api-idempotency-migrations"
php artisan migrate
```

---

Quick Start
-----------

[](#quick-start)

Apply the middleware to routes:

```
// routes/api.php
Route::post('/payments', [PaymentController::class, 'store'])
    ->middleware('idempotent');

Route::post('/orders', [OrderController::class, 'store'])
    ->middleware('idempotent:required'); // Key required
```

Client request with idempotency key:

```
curl -X POST https://api.example.com/payments \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pay_abc123_unique_key" \
  -d '{"amount": 9999, "currency": "EUR"}'
```

**First request:** Executes normally, response cached **Same key again:** Returns cached response, no re-execution

### Response Headers

[](#response-headers)

```
HTTP/1.1 201 Created
Idempotency-Key: pay_abc123_unique_key
X-Idempotent-Replayed: false
```

On replay:

```
HTTP/1.1 201 Created
Idempotency-Key: pay_abc123_unique_key
X-Idempotent-Replayed: true
X-Original-Request-Time: 2025-01-15T10:30:00+00:00
```

---

Middleware Options
------------------

[](#middleware-options)

```
// Custom TTL (seconds)
->middleware('idempotent:ttl=172800')  // 48 hours

// Require key (returns 400 if missing)
->middleware('idempotent:required')

// Custom scope
->middleware('idempotent:scope=team')

// Combined options
->middleware('idempotent:required,ttl=3600')
```

---

PHP Attributes
--------------

[](#php-attributes)

```
use Grazulex\ApiIdempotency\Attributes\Idempotent;
use Grazulex\ApiIdempotency\Attributes\IdempotentExcept;

#[Idempotent]
class PaymentController extends Controller
{
    public function store(Request $request) { /* ... */ }

    #[IdempotentExcept]
    public function index() { /* ... */ } // Excluded
}
```

---

Programmatic Usage
------------------

[](#programmatic-usage)

```
use Grazulex\ApiIdempotency\Facades\Idempotency;

// Check if already processed
if ($cached = Idempotency::get($key)) {
    return $cached->toResponse();
}

// Store manually
Idempotency::store($key, response()->json($data, 201));

// Skip caching (e.g., for validation errors)
Idempotency::skip();
```

---

Key Generation
--------------

[](#key-generation)

```
use Grazulex\ApiIdempotency\Support\IdempotencyKey;

// Generate unique key
$key = IdempotencyKey::generate();         // "idem_01HQ3K4M..."
$key = IdempotencyKey::generate('pay');    // "pay_01HQ3K4M..."

// Deterministic key from data
$key = IdempotencyKey::fromData([
    'user_id' => 123,
    'action' => 'create_payment',
]);
```

---

Artisan Commands
----------------

[](#artisan-commands)

```
# View statistics
php artisan idempotency:stats

# Cleanup expired keys
php artisan idempotency:cleanup

# Remove specific key
php artisan idempotency:forget pay_abc123

# List recent keys
php artisan idempotency:list --limit=20
```

---

Events
------

[](#events)

```
use Grazulex\ApiIdempotency\Events\IdempotentRequestProcessed;
use Grazulex\ApiIdempotency\Events\IdempotentRequestReplayed;
use Grazulex\ApiIdempotency\Events\IdempotentConflictDetected;
use Grazulex\ApiIdempotency\Events\IdempotentPayloadMismatch;
```

---

Configuration
-------------

[](#configuration)

```
// config/api-idempotency.php
return [
    'enabled' => env('API_IDEMPOTENCY_ENABLED', true),
    'header' => env('API_IDEMPOTENCY_HEADER', 'Idempotency-Key'),

    'key' => [
        'required' => false,
        'min_length' => 10,
        'max_length' => 255,
        'pattern' => '/^[a-zA-Z0-9_-]+$/',
    ],

    // Drivers: cache, redis, database, dynamodb
    'driver' => env('API_IDEMPOTENCY_DRIVER', 'cache'),

    'drivers' => [
        'cache' => [
            'store' => 'default',
            'prefix' => 'idempotency:',
        ],
        'redis' => [
            'connection' => 'default',
            'prefix' => 'idempotency:',
        ],
        'database' => [
            'connection' => null,
            'table' => 'idempotency_keys',
        ],
        'dynamodb' => [
            'table' => 'idempotency_keys',
            'region' => 'eu-west-1',
        ],
    ],

    'ttl' => env('API_IDEMPOTENCY_TTL', 86400), // 24 hours

    'conflict' => [
        'strategy' => 'wait', // or 'reject'
        'wait_timeout' => 10,
        'retry_interval' => 100,
    ],

    'fingerprint' => [
        'enabled' => true,
        'algorithm' => 'sha256',
        'include_path' => true,
        'include_method' => true,
        'include_body' => true,
        'exclude_fields' => ['timestamp', 'nonce'],
    ],

    'scope' => [
        'enabled' => true,
        'resolver' => 'user', // user, tenant, ip, or callable
    ],

    'logging' => [
        'enabled' => true,
        'log_hits' => true,
        'log_conflicts' => true,
    ],
];
```

---

Testing
-------

[](#testing)

```
use Grazulex\ApiIdempotency\Facades\Idempotency;

public function test_idempotency(): void
{
    Idempotency::fake();

    // Your test code...

    Idempotency::assertStored('expected_key');
    Idempotency::assertReplayed('expected_key');
    Idempotency::assertStoredCount(5);
}
```

Integration testing:

```
public function test_payment_is_idempotent(): void
{
    $key = 'test_key_' . uniqid();
    $payload = ['amount' => 9999];

    $response1 = $this->postJson('/api/payments', $payload, [
        'Idempotency-Key' => $key,
    ]);

    $response1->assertStatus(201)
        ->assertHeader('X-Idempotent-Replayed', 'false');

    $response2 = $this->postJson('/api/payments', $payload, [
        'Idempotency-Key' => $key,
    ]);

    $response2->assertStatus(201)
        ->assertHeader('X-Idempotent-Replayed', 'true')
        ->assertJson($response1->json());
}
```

---

Quality Tools
-------------

[](#quality-tools)

```
# Run tests
composer test

# Code style (Laravel Pint)
composer pint

# Static analysis (PHPStan Level 5)
composer analyse
```

---

Error Responses
---------------

[](#error-responses)

CodeStatusDescription`IDEMPOTENCY_KEY_MISSING`400Key required but not provided`IDEMPOTENCY_KEY_INVALID`400Key format invalid`IDEMPOTENCY_PAYLOAD_MISMATCH`422Same key, different payload`IDEMPOTENCY_CONFLICT`409Request in progress with same key---

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security
--------

[](#security)

If you discover any security-related issues, please email  instead of using the issue tracker.

Credits
-------

[](#credits)

- [Jean-Marc Strauven](https://github.com/Grazulex)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

---

See Also
--------

[](#see-also)

- [laravel-apiroute](https://github.com/Grazulex/laravel-apiroute) - API versioning lifecycle management
- [laravel-api-kit](https://github.com/Grazulex/laravel-api-kit) - API-only Laravel starter kit

###  Health Score

43

—

FairBetter than 91% of packages

Maintenance82

Actively maintained with recent releases

Popularity24

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

94d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/888105bd54b6b7f7905523a16a1d08eebc2e5d39b19a4c174b5961bb4d52929b?d=identicon)[Grazulex](/maintainers/Grazulex)

---

Top Contributors

[![Grazulex](https://avatars.githubusercontent.com/u/4521546?v=4)](https://github.com/Grazulex "Grazulex (11 commits)")

---

Tags

middlewareapilaravelduplicateretryidempotencyrfcidempotent

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/grazulex-laravel-api-idempotency/health.svg)

```
[![Health](https://phpackages.com/badges/grazulex-laravel-api-idempotency/health.svg)](https://phpackages.com/packages/grazulex-laravel-api-idempotency)
```

###  Alternatives

[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k12.1M99](/packages/laravel-pulse)[andreaselia/laravel-api-to-postman

Generate a Postman collection automatically from your Laravel API

1.0k586.2k3](/packages/andreaselia-laravel-api-to-postman)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9682.1M97](/packages/roots-acorn)[api-platform/laravel

API Platform support for Laravel

59126.4k6](/packages/api-platform-laravel)[dragon-code/laravel-json-response

Automatically always return a response in JSON format

1118.6k1](/packages/dragon-code-laravel-json-response)[shahghasiadil/laravel-api-versioning

Elegant attribute-based API versioning solution for Laravel applications with built-in deprecation management and version inheritance

2913.6k](/packages/shahghasiadil-laravel-api-versioning)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
