PHP cookbook · Intermediate
Receive and verify webhooks in PHP
Process payment provider webhooks safely: verify signatures, avoid duplicates and respond quickly.
Approach
Read the raw request body, verify the HMAC signature with the shared secret and a timestamp tolerance, deduplicate by event ID with a unique constraint, store the event and return 2xx quickly; process heavy work asynchronously.
Solution
<?php
declare(strict_types=1);
$payload = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_SIGNATURE'] ?? ''; // e.g. "t=1700000000,v1=hex..."
parse_str(str_replace(',', '&', $header), $parts);
$timestamp = (int) ($parts['t'] ?? 0);
$expected = hash_hmac('sha256', "{$timestamp}.{$payload}", getenv('WEBHOOK_SECRET'));
if (abs(time() - $timestamp) > 300 || !hash_equals($expected, (string) ($parts['v1'] ?? ''))) {
http_response_code(400);
exit('Invalid signature');
}
$event = json_decode($payload, true, flags: JSON_THROW_ON_ERROR);
try {
$pdo->prepare('INSERT INTO webhook_events (event_id, type, payload, received_at) VALUES (?, ?, ?, NOW())')
->execute([$event['id'], $event['type'], $payload]);
} catch (PDOException $e) {
if ($e->getCode() === '23505') { http_response_code(200); exit('Duplicate'); } // already received
throw $e;
}
http_response_code(200); // a worker processes webhook_events asynchronously
echo 'ok';
Pitfalls
- Verifying the signature against re-encoded JSON instead of the raw body.
- Doing slow work before responding, causing provider timeouts and retries.
- No replay protection (timestamp check).
Variations
- Use the provider's SDK verifier (e.g.,
\Stripe\Webhook::constructEvent). - Process synchronously only when the work is tiny and idempotent.
Further reading: Stripe: Webhooks
Reference: https://docs.stripe.com/webhooks
More PHP recipes
- Add CSRF protection to PHP forms
- Add health check endpoints to a PHP app
- Add structured logging with request IDs in PHP
- Build a secure password reset flow in PHP
- Cache expensive queries with Redis in PHP
- Call external APIs with timeouts and retries in PHP
- Handle file uploads securely in PHP
- Implement rate limiting in a PHP API