PHPackages                             davihedg/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. [Framework](/categories/framework)
4. /
5. davihedg/idempotency

ActiveLibrary[Framework](/categories/framework)

davihedg/idempotency
====================

Laravel middleware for idempotency

v1.0.0(3y ago)028MITPHPPHP ^8.0

Since Dec 2Pushed 3y agoCompare

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

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

🔄 Replay - Idempotency Middleware
=================================

[](#-replay---idempotency-middleware)

[![Latest Version on Packagist](https://camo.githubusercontent.com/388ea36d648ac6ceff425e9e9a6dd6866f03d30e3e93e2789af088b3d7498480/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f627674746572666c792f7265706c61792e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/bvtterfly/replay)[![GitHub Tests Action Status](https://camo.githubusercontent.com/09cf062ab90dd7b8955165a5cc03d4f2b52b9dfd3530be3fb082ae508002448f/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f776f726b666c6f772f7374617475732f627674746572666c792f7265706c61792f72756e2d74657374733f6c6162656c3d7465737473)](https://github.com/bvtterfly/replay/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/49679caa16ac4f3997f4213a5173dfd07a910cf5d4d7f3d46a586d27c97d0c53/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f776f726b666c6f772f7374617475732f627674746572666c792f7265706c61792f436865636b253230262532306669782532307374796c696e673f6c6162656c3d636f64652532307374796c65)](https://github.com/bvtterfly/replay/actions?query=workflow%3A%22Check+%26+fix+styling%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/61a80db35e2e8fab012bb679dada83ef63af71f0001cbeac23e9a6261097c587/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f627674746572666c792f7265706c61792e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/bvtterfly/replay)

This package makes your endpoints idempotent easily.

Check out this [Stripe Blog Post](https://stripe.com/blog/idempotency) about Idempotency.

Implementation inspired by [Stripe API](https://stripe.com/docs/api/idempotent_requests).

💡 Features
----------

[](#-features)

- Adding support idempotency requests to your APIs easily by adding a middleware.
- Works only for `POST` requests. Other endpoints are ignored.
- Record and replay only successful(2xx) and server-side errors(5xx) responses, without touching your controller again.
- it's safe to retry, it doesn't record the response with client-side errors (4xx).
- To prevent accidental misuse of the cached responses, the request's signature is validated to ensure that the cached response is returned using the same combination of Idempotency-Key and Request.
- Concurrency protection using Laravel's atomic locks to prevent race conditions.

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

[](#installation)

You can install the package via composer:

```
composer require bvtterfly/replay
```

You can publish the config file with:

```
php artisan vendor:publish --tag="replay-config"
```

This is the contents of the published config file:

```
use Bvtterfly\Replay\StripePolicy;

return [

    /*
    |--------------------------------------------------------------------------
    | Cache Store
    |--------------------------------------------------------------------------
    |
    | This option controls the cache store that gets used while Replay will store the
    | information required for it to function.
    | By default, Replay will use the default cache store.
    |
    | Please see config/cache.php for the list of all available Cache Stores.
    |
     */

    'use' => env('REPLAY_CACHE_STORE', config('cache.default')),

    /*
    |--------------------------------------------------------------------------
    | Replay Master Switch
    |--------------------------------------------------------------------------
    |
    | Replay is enabled by default,
    | Use this setting to enable/disable the Replay.
    |
    */

    'enabled' => env('REPLAY_ENABLED', true),

    /*
    |--------------------------------------------------------------------------
    | Expiration Seconds
    |--------------------------------------------------------------------------
    |
    | This value controls the number of seconds until an idempotency response
    | is considered expired.
    |
    | The default is set to 1 day.
    |
    */

    'expiration' => 60 * 60 * 24,

    /*
    |--------------------------------------------------------------------------
    | Request Header Name
    |--------------------------------------------------------------------------
    |
    | Replay will check this header name to determine
    | if a request is an Idempotency request.
    |
    */

    'header_name' => 'Idempotency-Key',

    /*
    |--------------------------------------------------------------------------
    | Response Header Name
    |--------------------------------------------------------------------------
    |
    | Replay will add this header to previously executed responses
    | that's being replayed from the server.
    |
    | Use null or empty, if you don't need to identify these responses.
    |
    */
    'replied_header_name' => 'Idempotent-Replayed',

    /*
    |--------------------------------------------------------------------------
    | Policy
    |--------------------------------------------------------------------------
    |
    | The policy determines whether a request is idempotent and whether the response should
    |  be recorded.
    |
    */

    'policy' => StripePolicy::class,

];
```

> **Note:** Replay needs a cache driver that supports [Cache Tags](https://laravel.com/docs/9.x/cache#cache-tags) &amp; [Atomic Locks](https://laravel.com/docs/9.x/cache#atomic-locks) features. Refer to [Laravel's documentation](https://laravel.com/docs/9.x/cache) to see if your driver supports these features.

Optionally, you can publish the translations using

```
php artisan vendor:publish --tag="replay-translations"
```

✨ Server Usage
--------------

[](#-server-usage)

The `Bvtterfly\Replay\Replay`-middleware must be registered in the kernel:

```
//app/Http/Kernel.php

protected $routeMiddleware = [
  ...
  'replay' => \Bvtterfly\Replay\Replay,
];
```

Next, For idempotent an endpoint, apply `replay` middleware to it:

```
Route::post('/payments', function () {
    //
})->middleware('replay');
```

By default, Replay stores the idempotent key as a cache key in the cache store, So all routes with replay middleware share the same cache key with an idempotent key. It's Okay to store it this way in most cases, but in some scenarios, we just need to separate them. In these scenarios, we can add a prefix to cache keys using middleware parameters:

```
Route::post('/payments', function () {
//
})->middleware('replay:payments');
```

### Custom Policy

[](#custom-policy)

Replay use Policy to determine whether a request is idempotent and whether the response should be recorded. By default, Replay includes and uses `StripePolicy` Policy. To create your custom policy, you first need to implement the `\Bvtterfly\Replay\Contracts\Policy` contract:

```
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

interface Policy
{
    public function isIdempotentRequest(Request $request): bool;

    public function isRecordableResponse(Response $response): bool;
}
```

If you want to view an example implementation take a look at the `StripePolicy` class.

For using this policy, We can change the `policy` in the config file.

✨ Client Usage
--------------

[](#-client-usage)

To perform an idempotent request, Client must provide an additional `Idempotency-Key : ` header with a unique key to the request.

it is recommended to:

- Use "V4 UUIDs" for the creation of the idempotency unique keys (e.g. `07cd2d27-e0dc-466f-8193-28453e9c3023`).
- Derive the key from a user-attached object, like the ID of a shopping cart. This provides a relatively straightforward way to protect against double submissions.

Once Replay detects a key, it'll look it up in cache store. If found, it will serve the same response without hitting your controller action again.

To identify a previously executed response that’s being replayed from the server, look for the header `Idempotent-Replayed: true`.

If Replay can't find the key, it attempts to acquire a cache lock and caches successful or server error responses. Still, if it can't acquire the lock, another request with the same key is already in progress, then it will respond with the HTTP Conflict response status code.

Resetting the Cache
-------------------

[](#resetting-the-cache)

If you need to manually reset the cache for this package, you may use the following artisan command:

```
php artisan replay:cache-reset
```

🧪 Testing
---------

[](#-testing)

```
composer test
```

Changelog
---------

[](#changelog)

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

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Ari](https://github.com/bvtterfly)
- [All Contributors](../../contributors)

License
-------

[](#license)

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

###  Health Score

24

—

LowBetter than 32% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity7

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 76.9% 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

1261d ago

### Community

Maintainers

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

---

Top Contributors

[![bvtterfly](https://avatars.githubusercontent.com/u/99682351?v=4)](https://github.com/bvtterfly "bvtterfly (30 commits)")[![davihedg](https://avatars.githubusercontent.com/u/80536804?v=4)](https://github.com/davihedg "davihedg (5 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (2 commits)")

---

Tags

laravelreplaybvtterfly

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

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

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

###  Alternatives

[laravel/horizon

Dashboard and code-driven configuration for Laravel queues.

4.2k84.2M225](/packages/laravel-horizon)[lunarstorm/laravel-ddd

A Laravel toolkit for Domain Driven Design patterns

17959.0k](/packages/lunarstorm-laravel-ddd)[bezhansalleh/filament-plugin-essentials

A collection of essential traits that streamline Filament plugin development by taking care of the boilerplate, so you can focus on shipping real features faster

27584.7k16](/packages/bezhansalleh-filament-plugin-essentials)[vormkracht10/laravel-mails

Laravel Mails can collect everything you might want to track about the mails that has been sent by your Laravel app.

24149.7k](/packages/vormkracht10-laravel-mails)[jonpurvis/squeaky

A Laravel Validation Rule to Help Catch Profanity.

706.0k](/packages/jonpurvis-squeaky)[blendbyte/filament-title-with-slug

TitleWithSlugInput - Easy Permalink Slugs for the FilamentPHP Form Builder (PHP / Laravel / Livewire)

1322.4k3](/packages/blendbyte-filament-title-with-slug)

PHPackages © 2026

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