Webhook Design
debt(d7/e5/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints list semgrep and postman as tools, but missing HMAC validation and non-idempotent consumers are logic-level issues — semgrep can catch structural patterns like missing signature checks, but idempotency gaps and silent retry loops typically only surface during code review or integration/load testing in staging or production.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix describes three distinct practices: HMAC validation, async queue processing, and idempotency via event ID. This isn't a single-line patch — it requires introducing a queue consumer, storing processed event IDs, and adding signature verification middleware, touching multiple layers of the application.
Closest to 'persistent productivity tax' (b5). Applies to web and api contexts broadly. Every webhook endpoint in the codebase must implement the same three patterns (signature validation, async processing, idempotency), and any developer adding a new webhook consumer must understand and follow these practices, creating an ongoing structural tax across relevant work streams.
Closest to 'serious trap' (t7). The misconception field explicitly states that developers believe webhooks are fire-and-forget, not realizing providers retry on failure and consumers must be idempotent. This contradicts the intuitive mental model of HTTP (one request, one response, move on). The consequence — duplicate payment charges, emails, or DB records — is severe, and the 'obvious' naive implementation (process inline on receipt) is wrong in at least three distinct ways simultaneously.
Also Known As
TL;DR
Explanation
Webhooks push events to consumer endpoints via HTTP POST. Design considerations: Signature verification (HMAC-SHA256 of the payload with a shared secret — prevents forged payloads), idempotency (the same event may be delivered multiple times — consumers must be idempotent), delivery guarantees (retry with exponential backoff on non-2xx responses), timeouts (consumer must respond within 5-30 seconds — queue slow processing), event ordering (not guaranteed — include timestamps and sequence numbers), and dead letter handling (failed deliveries after retries need alerting).
Common Misconception
Why It Matters
Common Mistakes
- Not verifying HMAC signature — any HTTP client can forge webhook payloads.
- Non-idempotent consumers — retries cause duplicate charges, emails, or database records.
- Slow processing in webhook handler — slow handlers cause timeouts and trigger retries.
- No dead letter handling — failed webhooks silently lost means missed events.
Code Examples
// No signature verification, not idempotent:
$payload = file_get_contents('php://input');
$event = json_decode($payload);
// No verification — anyone can POST fake events!
if ($event->type === 'payment.completed') {
$this->processPayment($event->data); // Not idempotent — retries = double charge!
}
// Secure webhook handler:
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
// Verify HMAC signature:
$expected = 'sha256=' . hash_hmac('sha256', $payload, WEBHOOK_SECRET);
if (!hash_equals($expected, $signature)) {
http_response_code(401); exit;
}
$event = json_decode($payload);
// Idempotency — skip if already processed:
if ($this->events->isProcessed($event->id)) {
http_response_code(200); exit; // Acknowledge but skip
}
// Respond 200 immediately, process asynchronously:
http_response_code(200);
ProcessWebhookJob::dispatch($event->id)->afterResponse();
$this->events->markProcessed($event->id);