PHPackages                             swiftmade/playback - 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. swiftmade/playback

ActiveLibrary[API Development](/categories/api)

swiftmade/playback
==================

Implement idempotent endpoints in Laravel à la Stripe

v1.0.0(3y ago)25624MITPHPPHP ^7.3|^8.0CI passing

Since Sep 23Pushed 3y ago1 watchersCompare

[ Source](https://github.com/swiftmade/playback)[ Packagist](https://packagist.org/packages/swiftmade/playback)[ Docs](https://github.com/swiftmade/playback)[ RSS](/packages/swiftmade-playback/feed)WikiDiscussions master Synced 3w ago

READMEChangelog (2)Dependencies (5)Versions (4)Used By (0)

Laravel Playback
================

[](#laravel-playback)

[![Latest Version on Packagist](https://camo.githubusercontent.com/e2ea6c485c1a5da5fdbb7663036d70b1aa826c96f504e98121cce13298f62cc0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f73776966746d6164652f706c61796261636b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/swiftmade/playback)[![GitHub Actions](https://github.com/swiftmade/playback/actions/workflows/test.yml/badge.svg)](https://github.com/swiftmade/playback/actions/workflows/test.yml/badge.svg)[![Total Downloads](https://camo.githubusercontent.com/286a2da86b0dc1c35960410f901b3c6d4385bed98edb83097d5fbb1c4c895c7d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f73776966746d6164652f706c61796261636b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/swiftmade/playback)

*Idempotent endpoints in Laravel à la Stripe.*

Playback gives you idempotent endpoints in Laravel, using Redis locks. [What's even idempotency, and why should I care?](https://stripe.com/docs/api/idempotent_requests)

Features
--------

[](#features)

- 📼 Records and plays back 2xx and 5xx responses, without running your controller code again.
- 🔐 Built-in validation to prevent attacks by stolen/guessed idempotency keys.
- ⚠️ Won't store the response if there was a validation error (4xx).
- 🏎 Prevents race conditions using atomical Redis locks.

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

[](#installation)

> 💡 Supports Laravel 8.x, Laravel 9.x on PHP 7.4, 8.0 or 8.1

1. You can install the package via composer:

```
composer require swiftmade/playback
```

2. Publish the config file (optional):

```
php artisan vendor:publish --provider="Swiftmade\Playback\PlaybackServiceProvider"
```

3. Add the playback cache store

Open `config/cache.php` and add a new store.

```
'stores' => [
    // ... other stores
    'playback' => [
        'driver' => 'redis',
        // 👇🏻 Caution!
        // You probably don't want to use the cache connection in production.
        // Playback cache can grow to a big size for busy applications.
        // Make sure your redis instance is ready.
        'connection' => 'cache',
    ],
]
```

💡 **Apply the middleware**

Just apply the `Swiftmade\Playback\Playback` middleware to your endpoints. There are many ways of doing it, so here's a link to the docs:

-

Use
---

[](#use)

Even when middleware is active on a route, it's business as usual unless the client sends an `Idempotency-Key` in their request header.

```
Idempotency-Key: preferrably uuid4, but anything flies

```

Once Playback detects a key, it'll look it up in redis. If found, it will serve the same response **without hitting your controller action again**. You can know that happened by looking at the response headers. If it contains `Is-Playback`, you know it's just a repetition.

If the key is not found during the lookup, a race begins. The first request to acquire the redis lock gets to process the request and cache the response. Any other unlucky requests that land during that time window will return `425` status code.

#### Errors:

[](#errors)

- **400 Bad Request**If you get back status `400`, it means your request was not identical to the cached one. It's the client's responsibility to repeat the exact same request. This is also why another user can't steal a response just by stealing/guessing the idempotency key. The cookies/authentication token would be different, which fails the signature check.
- **425 Too Early**If you get this error, it means you retried too fast after your initial attempt. Don't panic and try again a second later or so. It's perfectly safe to do so!

🚨 Pro tip: If your controller action returns 4xx or 3xx status code, Playback won't cache the response. It's your responsibility to ensure no side effects take place (or they are rolled back) if a validation fails, a related db record was not found, etc and therefore the response status is 4xx or 3xx.

### Testing

[](#testing)

```
composer test
```

### Changelog

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information 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)

- [Ahmet Özisik](https://github.com/swiftmade)
- [All Contributors](../../contributors)

License
-------

[](#license)

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

###  Health Score

32

—

LowBetter than 69% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity23

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity62

Established project with proven stability

 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

Every ~337 days

Total

3

Last Release

1435d ago

Major Versions

v0.2.0 → v1.0.02022-07-29

PHP version history (2 changes)v0.1.0PHP ^7.3

v1.0.0PHP ^7.3|^8.0

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/735011?v=4)[Ahmet Özışık](/maintainers/aozisik)[@aozisik](https://github.com/aozisik)

---

Top Contributors

[![aozisik](https://avatars.githubusercontent.com/u/735011?v=4)](https://github.com/aozisik "aozisik (9 commits)")

---

Tags

idempotencyidempotent-requestslaravellaravelidempotent

###  Code Quality

TestsPHPUnit

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/swiftmade-playback/health.svg)

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

###  Alternatives

[defstudio/telegraph

A laravel facade to interact with Telegram Bots

816333.3k3](/packages/defstudio-telegraph)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5021.9k](/packages/simplestats-io-laravel-client)[riclep/laravel-storyblok

A Laravel wrapper around the Storyblok API to provide a familiar experience for Laravel devs

6279.6k5](/packages/riclep-laravel-storyblok)

PHPackages © 2026

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