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 設計 流程應該是:
- 驗證簽名 (Middleware)。
- 接收 Payload。
- 將 Payload 丟入 Queue (Job)。
- 立刻回傳
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 安全性不足?
別讓技術問題阻礙業務發展,現在就聯繫浪花科技,讓我們為你打造最堅固的系統架構!
常見問題
Laravel 接收 Webhook 需要哪些防護機制?
Laravel 11 要在哪裡註冊 Webhook 驗證的 Middleware?
Webhook 為什麼要把工作丟進 Queue 而不是直接處理?
什麼是 Webhook 的冪等性?要怎麼實作?
Laravel Webhook 簽名驗證為什麼要用 hash_equals 比對?
訂閱免費電子報
把 AI 自動化、企業系統設計與 WordPress / Laravel 開發的真實案例和可直接照做的技巧,整理成電子報寄給你。只寄精選內容、不灌垃圾信,一鍵就能退訂。