bluebeetle/idempotency-middleware
Laravel middleware for idempotent API endpoints that detects duplicate requests by idempotency key and replays the original response. Makes retried POST, payment, and webhook calls safe from double processing with minimal setup.
Architecture fit. The package targets a well-defined, recurring gap in Laravel HTTP applications: making non-idempotent endpoints (POST/PATCH) safe to retry. It slots in as global or route-level middleware, which is idiomatic for the framework and requires no changes to controllers or the service container beyond registration. That is the correct seam — idempotency is a cross-cutting transport concern, not domain logic — so the conceptual architecture is sound and low-friction for a standard Laravel app.
Integration feasibility. Mechanically easy: composer require, register middleware in bootstrap/app.php (Laravel 11+) or Kernel.php, supply an Idempotency-Key header from clients, and choose a persistence store. The real feasibility question is not wiring but semantics: does the package persist the response body and status for replay, does it lock the key for the duration of the in-flight request, and what is the key scoping (per-user, per-route, global)? Those determine whether the middleware prevents duplicate side effects or merely dedupes responses after the side effect already occurred — the latter is worse than nothing because it creates false confidence.
Technical risk. High, and concentrated in project maturity rather than design. Three stars, zero dependents, and a single maintainer mean effectively no production validation and no ecosystem pressure to keep it correct across Laravel majors. Idempotency is a correctness-critical path: a subtle bug in lock acquisition or TTL handling shows up as duplicated payments or duplicated provisioning, not as a failed test. The release date metadata is also inconsistent with a normal timeline, which for a TPM is a housekeeping smell worth verifying against the actual tag history. There is no evidence of a security review, and an idempotency layer by definition stores and replays request/response payloads — a data-retention and PII surface that must be scoped deliberately.
Key questions. Ask before committing:
Stack fit. Good for any Laravel 10/11/12 service that sits behind a retrying client — mobile apps with aggressive retry logic, payment and checkout flows, webhook receivers, or queue-dispatched HTTP calls. It is a poor fit if the app relies on a non-standard HTTP kernel, uses Octane with long-lived workers and in-process state, or has no shared cache/DB available for cross-node locking.
Migration path. Treat this as a pattern adoption, not a package adoption. The durable asset here is the discipline of requiring an Idempotency-Key header and defining replay semantics; the package is one implementation of it. Start by inventorying endpoints that mutate state non-idempotently and rank by blast radius (money first, then provisioning, then notifications). Where we already have ad-hoc idempotency (unique constraints, dedupe tables, client-side nonces), document the existing behavior before overlaying anything, so we don't end up with two competing dedupe layers disagreeing.
Compatibility. Pin the version explicitly and verify the tested matrix against our Laravel minor version in a spike branch. The store choice drives most compatibility risk: Redis with atomic SET NX is the strongest option if we already run Redis; database-backed storage is simpler but adds write load to the primary and needs a cleanup job. Avoid file/database cache drivers in any multi-node setup.
Sequencing.
Maintenance. The dominant cost is not the code but the fork obligation. With one maintainer and zero dependents, assume we will own this within 12 months: tracking Laravel majors, PHP version bumps, and security patches ourselves. Budget a small but permanent maintenance line, and require the wrapper interface in step 3 so a future replacement is a swap, not a refactor. Vendoring or forking under our org with a named owner is the honest operating model.
Support. No vendor, no SLA, community-only issue response. Internal support means our platform team becomes tier-1 for "why did my retry get a 409 / a stale response / a duplicated charge." That requires a runbook and clear logging of key, scope, store, and replay decision on every request — verify the package emits usable log events, and add them if not.
Scaling. Failure modes surface at concurrency, not volume. The critical properties are atomic lock acquisition, lock expiry longer than the slowest handler, and a response store that survives node failure. Under load, watch for lock contention on hot keys, unbounded growth of stored responses (TTL and cleanup are mandatory, not optional), and Redis memory pressure if bodies are large. If the store is the primary database, idempotency writes will contend with business writes at peak — size for it or use Redis.
Failure modes to design against. Duplicate side effect on lock timeout; replayed response after the underlying resource has since changed or been deleted; key reuse by a buggy client returning a stale result for a genuinely new intent; TTL expiring mid-flight so a late retry re-executes; cross-tenant collision if scoping is route-only; and the silent failure — middleware enabled but clients not sending keys, giving zero protection with a false sense of safety. Each needs an explicit decision and a test.
Ramp-up. Low engineering ramp-up (a day for a Laravel developer), moderate organizational ramp-up (client teams must adopt the header, and API docs, SDKs, and mobile clients all need updating). The real cost is coordination, not code. Net recommendation: worth a one-week spike as a reference implementation and pattern-setter; do not adopt as an unexamined dependency on a payment path. Given the maturity signals, plan for build-with-reference rather than buy.
How can I help you explore Laravel packages today?