~/blog/laravel-webhook-design-verification-guide-laravel-11.md
Laravel 與後端開發 · 2026 / 01 / 14

Laravel Webhook 實戰指南:拒絕無防護上線,打造高安、高併發的接收端

Eric — 浪花科技創辦人 / AI 架構師
Eric
浪花科技創辦人 · AI 架構師
Laravel Webhook 實戰指南:拒絕無防護上線,打造高安、高併發的接收端
目錄 table-of-contents.md

幫客戶做系統對接時,最讓人血壓飆高的畫面莫過於:一個毫無驗證機制的 Webhook 接口大剌剌開在公網上,任何人都能對它塞假資料。Webhook 接收端不只要擋得住偽造請求,還得扛得住瞬間湧入的高併發。這篇 Laravel 實戰指南,就帶你把驗證與非同步消化一次做好,拒絕無防護上線。

Webhook 是現代 Web 應用的神經網路,連接著 Stripe、LINE、Slack 或是你自家的微服務。但是,很多人在寫 Laravel Webhook 時,往往只求「能收到資料就好」,忽略了最重要的設計與驗證。今天這篇不講虛的,我們直接用 Laravel 11 的最新架構,聊聊如何優雅地處理 Laravel Webhook 設計與驗證,讓你的系統穩如泰山。

為什麼你的 Webhook 不能「裸奔」?

在討論程式碼之前,先要有個共識:永遠不要信任來自客戶端的資料,即便那個「客戶端」聲稱自己是 GitHub 或 Stripe。如果你的 Webhook URL 洩漏出去(這在 Log 裡很常見),任何人都可以用 Postman 對你的伺服器發送假訂單、假付款通知。這不是危言聳聽,這是我們在實戰中看過無數次的慘劇。

一個合格的 Webhook 接收端設計,必須包含以下三個防護層:

  • 簽名驗證 (Signature Verification): 確保資料真的是由預期的發送方寄出的,且內容未被竄改。
  • 時效性檢查 (Timestamp Check): 防止重放攻擊 (Replay Attack),避免黑客擷取舊的請求重複發送。
  • 冪等性處理 (Idempotency): 確保同一個事件只被處理一次(網路抖動時這救命關鍵)。

實戰:Laravel 11 Middleware 簽名驗證

很多新手會把驗證邏輯寫在 Controller 裡,這讓程式碼變得很髒。在 Laravel,最優雅的方式絕對是使用 Middleware。這裡我們以「HMAC SHA256」為例,這是目前最通用的驗證標準。

1. 建立 Middleware

在 Laravel 11 中,我們可以快速建立一個驗證中介層:

php artisan make:middleware VerifyWebhookSignature

接著,編輯生成的檔案。這裡的重點是使用 hash_hmac 來比對簽名。Eric 提醒你,記得要把 Secret Key 放在 .env 裡,千萬別寫死在程式碼中!

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

class VerifyWebhookSignature
{
    public function handle(Request $request, Closure $next): Response
    {
        // 1. 取得 Header 中的簽名 (名稱依據服務商不同,例如 Stripe 是 Stripe-Signature)
        $signature = $request->header('X-Hub-Signature-256');

        if (!$signature) {
            throw new AccessDeniedHttpException('Missing Signature');
        }

        // 2. 取得 Payload
        $payload = $request->getContent();
        
        // 3. 取得你的密鑰
        $secret = config('services.webhook.secret');

        // 4. 計算預期簽名 (注意:有些服務商會在簽名前加 'sha256=')
        $expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);

        // 5. 使用 hash_equals 進行安全比對 (防止時序攻擊)
        if (!hash_equals($expected, $signature)) {
            throw new AccessDeniedHttpException('Invalid Signature');
        }

        return $next($request);
    }
}

2. 在 Laravel 11 中註冊 Middleware

Laravel 11 移除了 Kernel.php,現在我們要在 bootstrap/app.php 裡進行設定。這是很多從 Laravel 9/10 升級上來的工程師容易卡關的地方。

// bootstrap/app.php

use App\Http\Middleware\VerifyWebhookSignature;

->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'webhook.verify' => VerifyWebhookSignature::class,
    ]);
    
    // 重要:Webhook 通常是 API 請求,記得排除 CSRF 保護
    $middleware->validateCsrfTokens(except: [
        'webhook/*',
    ]);
})

別讓你的 Controller 塞車:佇列(Queue)的重要性

Webhook 的設計有一個黃金法則:「快進快出」。發送方(如 Stripe)通常只會等你幾秒鐘,如果超時就會判定失敗並重試。如果你在 Controller 裡做生成 PDF、寄信、寫入複雜資料庫等耗時操作,你的 Server 很快就會被重試的請求給塞爆。

正確的 Laravel Webhook 設計 流程應該是:

  1. 驗證簽名 (Middleware)。
  2. 接收 Payload。
  3. 將 Payload 丟入 Queue (Job)。
  4. 立刻回傳 200 OK
// WebhookController.php

public function handle(Request $request)
{
    // 驗證已在 Middleware 完成
    $payload = $request->all();

    // 將繁重的工作丟給 Queue,Eric 強烈建議使用 Redis 作為驅動
    ProcessWebhookJob::dispatch($payload)->onQueue('webhooks');

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

最後一道防線:冪等性 (Idempotency)

這是我看過最容易被忽略的坑。網路是不穩定的,發送方可能會因為沒收到你的回應而重複發送同一個 Webhook。如果你沒有做冪等性檢查,客戶可能會被重複扣款,或者訂單會被重複建立。

解決方法很簡單:利用 Webhook ID。

大多數 Webhook Payload 都會包含一個唯一的 Event ID。在處理 Job 之前,先去 Cache (Redis) 或資料庫檢查這個 ID 是否處理過。

// ProcessWebhookJob.php

public function handle()
{
    $eventId = $this->payload['id'];

    // 使用 atomic lock 確保併發安全
    $lock = Cache::lock('webhook_processed_'.$eventId, 10);

    if (!$lock->get()) {
        // 已經在處理中或處理過,直接 return
        return;
    }

    try {
        // 執行你的業務邏輯...
        // ...
        
        // 標記為永久已處理
        Cache::forever('webhook_done_'.$eventId, true);
    } catch (\Exception $e) {
        // 處理失敗,釋放 lock 讓它可以重試
        $lock->release();
        throw $e;
    }
}

Eric 的小囉嗦總結

Webhook 看似簡單,但要做到「生產環境等級」的穩定性,細節非常多。從Laravel Webhook 設計與驗證、Middleware 的抽離、CSRF 的排除,到 Queue 的非同步處理以及冪等性的防護,每一個環節都決定了你的系統是堅固的堡壘還是漏水的篩子。

別為了省事而跳過驗證步驟,技術債這東西,遲早是要還的,而且通常還得連本帶利。

延伸閱讀

你的 Laravel 系統對接遇到瓶頸了嗎?或是擔心 Webhook 安全性不足?
別讓技術問題阻礙業務發展,現在就聯繫浪花科技,讓我們為你打造最堅固的系統架構!

立即諮詢 Eric 團隊
// FAQ

常見問題

Laravel 接收 Webhook 需要哪些防護機制?
一個合格的 Webhook 接收端應包含三個防護層:簽名驗證(確認來源真實且內容未被竄改)、時效性檢查(用時間戳防止重放攻擊)、以及冪等性處理(確保同一事件只被處理一次)。缺一個都可能導致資料被偽造、重複扣款或重複建單。
Laravel 11 要在哪裡註冊 Webhook 驗證的 Middleware?
Laravel 11 移除了 Kernel.php,改在 bootstrap/app.php 的 withMiddleware 區塊中設定。可用 $middleware->alias() 註冊中介層別名,並用 $middleware->validateCsrfTokens(except: [...]) 將 Webhook 路徑排除在 CSRF 保護之外。這是從 Laravel 9/10 升級者常卡關的地方。
Webhook 為什麼要把工作丟進 Queue 而不是直接處理?
Webhook 設計的黃金法則是「快進快出」。發送方通常只等幾秒,超時就判定失敗並重試。若在 Controller 裡做生成 PDF、寄信等耗時操作,重試請求會把伺服器塞爆。正確流程是驗證簽名、接收 Payload、丟入 Queue,然後立刻回傳 200 OK,繁重工作交給背景 Job 處理。
什麼是 Webhook 的冪等性?要怎麼實作?
冪等性指同一個事件即使被重複發送,也只會被實際處理一次,避免重複扣款或重複建立訂單。實作方式是利用 Payload 中的唯一 Event ID,在處理前先到 Cache 或資料庫檢查該 ID 是否處理過;Laravel 中可搭配 Cache::lock() 取得原子鎖確保併發安全,處理完成後標記為已處理。
Laravel Webhook 簽名驗證為什麼要用 hash_equals 比對?
hash_equals 是固定時間比較函式,可避免時序攻擊(Timing Attack)。一般的字串比較會在第一個不同字元就提早返回,攻擊者可藉由回應時間差推測簽名內容;hash_equals 不論內容如何都耗用相同時間比較,因此用於比對 HMAC 簽名較安全。
~/roamer-tech/newsletter // FREE
// newsletter

訂閱免費電子報

把 AI 自動化、企業系統設計與 WordPress / Laravel 開發的真實案例和可直接照做的技巧,整理成電子報寄給你。只寄精選內容、不灌垃圾信,一鍵就能退訂。

$
// final.exec()

準備好讓你的網站開始為你工作了嗎?