PHPackages                             dabergut/laravel-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. [Utility &amp; Helpers](/categories/utility)
4. /
5. dabergut/laravel-idempotency

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

dabergut/laravel-idempotency
============================

Extracting duplicated request handling so you don't have to.

00PHPCI passing

Since Feb 18Pushed 2mo agoCompare

[ Source](https://github.com/dabergut/laravel-idempotency)[ Packagist](https://packagist.org/packages/dabergut/laravel-idempotency)[ RSS](/packages/dabergut-laravel-idempotency/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (0)

Laravel Idempotency
===================

[](#laravel-idempotency)

[![Tests](https://github.com/dabergut/-laravel-idempotency/actions/workflows/tests.yml/badge.svg)](https://github.com/dabergut/-laravel-idempotency/actions)

Drop-in middleware that prevents duplicate POST/PATCH processing in your Laravel API.

Client sends an `Idempotency-Key` header, server stores the response. Same key comes in again — cached response goes back, controller never fires twice. No double charges, no duplicate orders, no angry customers.

Why
---

[](#why)

Every API that mutates state has the same problem: the client sends a request, something hiccups (timeout, flaky connection, eager retry logic), and the same request arrives twice. Without idempotency handling your API happily processes it again.

You can write this yourself. I've done it about four times across different projects before extracting it into this package. The tricky bits are locking (concurrent duplicate requests), body fingerprinting (same key, different payload), and scoping keys per user.

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

[](#requirements)

- PHP 8.2+
- Laravel 11 or 12

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

[](#installation)

```
composer require dabergut/laravel-idempotency
```

The service provider registers automatically. Publish the config if you need to tweak anything:

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

Usage
-----

[](#usage)

Add the middleware to routes that shouldn't be processed twice:

```
Route::post('/orders', CreateOrderController::class)
    ->middleware('idempotent');
```

Or apply it to a group:

```
Route::middleware('idempotent')->group(function () {
    Route::post('/orders', CreateOrderController::class);
    Route::post('/payments', ProcessPaymentController::class);
    Route::patch('/orders/{order}', UpdateOrderController::class);
});
```

That's it. Your clients need to send an `Idempotency-Key` header with their requests:

```
POST /api/orders HTTP/1.1
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{"product_id": 42, "quantity": 1}

```

First request processes normally. Second request with the same key returns the stored response without hitting your controller.

Response headers
----------------

[](#response-headers)

Every response from an idempotent endpoint includes:

HeaderValueMeaning`Idempotent-Replayed``false`Fresh response, controller executed`Idempotent-Replayed``true`Cached response, controller skippedWhat happens when
-----------------

[](#what-happens-when)

ScenarioResultNo `Idempotency-Key` headerRequest processed normally, no cachingKey present, first time seenRequest processed, response cachedKey present, seen before, same bodyCached response returned (201, not 200)Key present, seen before, different body422 error — key reuse with different payloadKey too short (&lt; 8 chars by default)422 errorConcurrent duplicate requestsSecond request waits for lock, then returns cached responseGET or DELETE requestMiddleware does nothing (idempotent by HTTP spec)Configuration
-------------

[](#configuration)

```
// config/idempotency.php

return [
    // Header name. Stripe uses the same one.
    'header' => 'Idempotency-Key',

    // How long to keep stored responses (minutes). Default: 24 hours.
    'ttl' => 1440,

    // Cache store. null = your default driver. Redis recommended.
    'store' => null,

    // Which HTTP methods to enforce on.
    'methods' => ['POST', 'PATCH'],

    // Minimum key length. UUIDs are 36 chars.
    'min_key_length' => 8,

    // Reject reused keys with different request bodies.
    'enforce_body_match' => true,
];
```

User scoping
------------

[](#user-scoping)

Keys are automatically scoped to the authenticated user. User A and User B can both send `Idempotency-Key: abc` without collision. Unauthenticated requests share a global scope — keep that in mind if your public endpoints use this middleware.

Cache backend
-------------

[](#cache-backend)

This works with any Laravel cache driver, but **use Redis in production**. File cache works for development but won't survive deployments and doesn't support atomic locks properly.

```
IDEMPOTENCY_STORE=redis
```

Testing
-------

[](#testing)

```
composer test
```

License
-------

[](#license)

MIT

###  Health Score

18

—

LowBetter than 8% of packages

Maintenance55

Moderate activity, may be stable

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity12

Early-stage or recently created project

 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.

### Community

Maintainers

![](https://www.gravatar.com/avatar/f00fdc844e9c706b1096104ba788666f94fa44432abc6e628edd318f2f5319a2?d=identicon)[piodab](/maintainers/piodab)

---

Top Contributors

[![piodab](https://avatars.githubusercontent.com/u/9605827?v=4)](https://github.com/piodab "piodab (2 commits)")

### Embed Badge

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

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

###  Alternatives

[magirc/magirc

MagIRC - Let the magirc begin!

381.1k](/packages/magirc-magirc)[bjornjohansen/wp-pre-commit-hook

Pre-commit hook for WordPress projects

118.8k3](/packages/bjornjohansen-wp-pre-commit-hook)[nicolaswurtz/chordpro-php

Parse, transpose and format (html,json,plaintext) ChordPro format for songs lyrics with chords.

173.6k](/packages/nicolaswurtz-chordpro-php)

PHPackages © 2026

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