惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
IT之家
IT之家
Recent Announcements
Recent Announcements
B
Blog
D
Docker
V
V2EX
GbyAI
GbyAI
L
LangChain Blog
博客园 - Franky
U
Unit 42
T
The Blog of Author Tim Ferriss
A
About on SuperTechFans
博客园 - 【当耐特】
Google DeepMind News
Google DeepMind News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Vercel News
Vercel News
博客园_首页
D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
Y
Y Combinator Blog
量子位
Blog — PlanetScale
Blog — PlanetScale
罗磊的独立博客

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Defeating Webhook Storms: Idempotency in Laravel 🛑
Prajapati Paresh · 2026-06-20 · via DEV Community

The Double-Billing Nightmare

When integrating with enterprise payment processors like Stripe or enterprise CRMs at Smart Tech Devs, relying on webhooks is mandatory. However, distributed systems are inherently chaotic. If Stripe sends your API a charge.succeeded webhook, it expects a 200 OK HTTP response within 3 seconds.

If your Laravel server takes 4 seconds to provision the user's workspace, Stripe assumes the delivery failed and fires a retry. Now your server is processing the exact same $5,000 enterprise charge a second time. Suddenly, you have provisioned two workspaces, dispatched two receipts, and corrupted your accounting ledger. To survive these inevitable retry storms, your webhook endpoints must be architected for Idempotency.

What is Idempotency?

In mathematics and computer science, an idempotent operation is one that produces the exact same result whether it is executed once or ten thousand times.

To achieve this in Laravel, we use the unique Event ID provided by the vendor (e.g., Stripe's evt_12345) as an Idempotency Key. When a webhook arrives, we instantly lock that Event ID in our database or Redis cache. If a retry arrives 2 seconds later while the first job is still processing, our system sees the lock, ignores the duplicate payload, and returns a graceful 200 OK to satisfy the vendor.

Step 1: Architecting the Idempotent Job

We offload the webhook processing to a background Queue Worker to ensure we reply to the vendor instantly, and we wrap the actual business logic inside a strict atomic Redis lock.


namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Cache;

class ProcessStripePayment implements ShouldQueue
{
    use Dispatchable, Queueable;

    public array $webhookPayload;

    public function __construct(array $payload)
    {
        $this->webhookPayload = $payload;
    }

    public function handle(): void
    {
        // 1. Extract the globally unique Event ID from the vendor
        $eventId = $this->webhookPayload['id'];
        $lockKey = "webhook_processing_{$eventId}";

        // 2. ATOMIC LOCK: We attempt to acquire a lock for 10 minutes.
        // The get() method returns false immediately if another worker already holds this lock!
        $lock = Cache::lock($lockKey, 600);

        if (! $lock->get()) {
            // A retry storm is happening! Another thread is already handling this exact event.
            \Log::info("Idempotency Triggered: Silently dropping duplicate webhook {$eventId}.");
            return;
        }

        try {
            // 3. We hold the lock. Execute the critical business logic ONCE.
            \Log::info("Processing payment for Event {$eventId}...");
            
            // Provision workspace, send receipt, update ledger...
            
            // 4. Record permanent success so future identical webhooks (days later) are also ignored
            Cache::put("webhook_completed_{$eventId}", true, now()->addDays(30));

        } catch (\Exception $e) {
            // 5. If our logic FAILS, we release the lock so the next Stripe retry can safely attempt it again.
            $lock->release();
            throw $e;
        }
    }
}

Step 2: The Controller Hand-off

Your API controller is now purely a traffic cop. It verifies the signature, dispatches the job, and immediately hangs up the phone to prevent vendor timeouts.


public function handleWebhook(Request $request)
{
    // (Assuming HMAC signature validation middleware has already passed)
    
    // Check if we already permanently completed this event in the past
    $eventId = $request->input('id');
    if (Cache::has("webhook_completed_{$eventId}")) {
        return response()->json(['status' => 'already_processed']);
    }

    // Dispatch the idempotent job and reply in < 50ms
    ProcessStripePayment::dispatch($request->all());

    return response()->json(['status' => 'queued']);
}

The Engineering ROI

Idempotency is the ultimate safety net for distributed systems. By relying on atomic Redis locks and vendor-supplied Event IDs, you completely decouple your application's data integrity from the unpredictability of network latency. You eliminate the risk of double-billing, prevent database corruption, and build an API that gracefully absorbs massive retry storms without breaking a sweat.