# Idempotency

> Install, configure, and apply the Laravel Idempotency package in Laravel applications when you need retry-safe POST, PUT, or PATCH endpoints.

- Skill: `wendelladriel/idempotency` (Agent Skill)
- Install (CLI): `npx skillmds@latest add wendelladriel/idempotency`
- Raw SKILL.md: https://api.skillmd.com/api/skills/wendelladriel/idempotency/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: wendelladriel (https://skillmd.com/u/wendelladriel)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/wendelladriel/idempotency

---


# Idempotency

Use this skill when a Laravel application needs retry-safe write requests, idempotency keys, or controller attributes.

## Primary Goal

- install and configure `wendelladriel/laravel-idempotency` correctly
- choose the smallest correct integration path for the app
- apply middleware, attributes, key inputs, and scopes using the package's public API

## Workflow

### 1. Inspect the Laravel app context

- confirm the app is a Laravel project
- inspect the target routes or controllers that create or update data
- identify whether the endpoint is better served by route middleware or a controller attribute
- inspect the app cache driver before enabling idempotency, since it must support atomic locks and be shared by every application server

### 2. Install and publish configuration when needed

- install the package with `composer require wendelladriel/laravel-idempotency`
- publish the config with `php artisan vendor:publish --tag=idempotency-config` when the app needs non-default behavior
- use these config keys when customization is required:
  - `idempotency.ttl`
  - `idempotency.required`
  - `idempotency.scope`
  - `idempotency.header`
  - `idempotency.input`
  - `idempotency.strict_index_locks`

### 3. Choose the integration style

Prefer route middleware when:

- only a few endpoints need idempotency
- the behavior belongs directly on a route definition
- the app already configures most middleware in the route files

Use the controller attribute when:

- the app groups related write actions inside a controller
- class-level defaults with method-level overrides make the intent clearer
- `only` or `except` targeting is useful

### 4. Apply route middleware

Use the package middleware class:

```php
use WendellAdriel\Idempotency\Http\Middleware\Idempotent;

Route::post('/orders', StoreOrderController::class)
    ->middleware(Idempotent::class);
```

When the endpoint needs overrides, use `Idempotent::using(...)`:

```php
Route::post('/payments', ChargePaymentController::class)
    ->middleware(Idempotent::using(
        ttl: 600,
        required: false,
        scope: \WendellAdriel\Idempotency\Enums\IdempotencyScope::Ip,
        header: 'X-Idempotency-Key',
    ));
```

The package reads the key from the configured header first. When the header does not contain a non-empty string, it falls back to the configured request input, which defaults to `_idempotency_key`. Use the request input for standard HTML forms that cannot set custom headers.

Use the `@idempotency` Blade directive to render the configured hidden input with a generated key:

```blade
<form method="POST" action="/orders">
    @csrf
    @idempotency
</form>
```

Pass an existing key with `@idempotency($key)` when the application generates or stores the key elsewhere.

### 5. Apply the controller attribute

Use the package attribute on classes or methods:

```php
use WendellAdriel\Idempotency\Attributes\Idempotent;

#[Idempotent]
class OrderController
{
    public function store()
    {
        // ...
    }
}
```

Use method-level attributes to override class-level defaults:

```php
use WendellAdriel\Idempotency\Enums\IdempotencyScope;

#[Idempotent]
class PaymentController
{
    #[Idempotent(ttl: 600, scope: IdempotencyScope::Global)]
    public function store()
    {
        // ...
    }
}
```

### 6. Choose the correct scope

- use `user` when authenticated users should not collide with each other; guests fall back to IP
- use `ip` for guest-heavy flows where IP segmentation is enough
- use `global` when the same key should replay across users or IPs

### 7. Support key generation

- generate a random key in app code with `WendellAdriel\Idempotency\Idempotency::key()` when the server needs to create keys

### 8. Verify the integration

- verify repeated `POST`, `PUT`, or `PATCH` requests with the same key replay the original response
- verify mismatched payloads return `422`
- verify in-flight duplicate requests return `409` with `Retry-After: 1`
- verify requests without a valid header or request-input key only pass through when `required` is disabled

### 9. Maintain cached entries

Use the maintenance commands when debugging replay behavior, purging entries after a compromised key, or inspecting cache usage during incident response.

- inspect cached entries with `php artisan idempotency:list`
- filter listings by scope or identity when the cache is large:

```bash
php artisan idempotency:list --scope=user --id=5
php artisan idempotency:list --scope=global
php artisan idempotency:list --limit=20
```

- purge entries when replay behavior is the wrong thing to keep:

```bash
# remove everything (prompts unless --force is passed)
php artisan idempotency:forget --all --force

# scoped removal
php artisan idempotency:forget --scope=user --id=5 --force
php artisan idempotency:forget --scope=ip --id=1.2.3.4 --force
php artisan idempotency:forget --scope=global --force

# purge everything tied to a single client-provided key
php artisan idempotency:forget --key=checkout-1 --force
```

## Rules, References, and Templates

Read before executing:

- no additional resource files for this skill

## Examples

- add idempotency to payment or order creation endpoints
- protect retryable webhook receivers

## Anti-patterns

- do not apply idempotency to read-only routes
- do not suggest unsupported scope values beyond `user`, `ip`, and `global`
- do not document package maintenance tasks here; keep the skill focused on package adoption in Laravel apps
- do not assume every cache driver supports atomic locks without checking
- do not use the `array` driver outside tests or single-process development

