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)25620MITPHPPHP ^7.3|^8.0

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 1w 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

31

—

LowBetter than 68% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity22

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity61

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

1389d 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

[andreaselia/laravel-api-to-postman

Generate a Postman collection automatically from your Laravel API

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

Mollie API client wrapper for Laravel &amp; Mollie Connect provider for Laravel Socialite

3624.1M28](/packages/mollie-laravel-mollie)[api-ecosystem-for-laravel/dingo-api

A RESTful API package for the Laravel and Lumen frameworks.

3121.5M10](/packages/api-ecosystem-for-laravel-dingo-api)[essa/api-tool-kit

set of tools to build an api with laravel

52680.5k](/packages/essa-api-tool-kit)[mll-lab/laravel-graphiql

Easily integrate GraphiQL into your Laravel project

683.2M9](/packages/mll-lab-laravel-graphiql)[kirschbaum-development/laravel-openapi-validator

Automatic OpenAPI validation for Laravel HTTP tests

581.1M5](/packages/kirschbaum-development-laravel-openapi-validator)

PHPackages © 2026

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