Webhook Replay Window Check
Validates a webhook timestamp and signature so replayed or stale callback requests can be rejected.
Purpose
Validates a webhook timestamp and signature so replayed or stale callback requests can be rejected.
Snippet details
ContextSecurityLevelAdvancedCopy-and-paste statusMarked safe after review.Categories
- Security
Code
<?php
/*
* Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
* Free for personal and internal use. Paid project use requires visible credit
* to Jeffery L. Paris. Corporate use requires a paid license fee unless a
* separate written license states otherwise.
*/
/**
* Webhook Replay Window Check.
*
* Purpose:
* Checks whether a signed webhook request is fresh and matches the expected
* HMAC signature.
*
* @param string $body Raw webhook request body.
* @param string $timestamp Header timestamp sent by the provider.
* @param string $signature Header signature sent by the provider.
* @param string $secret_key Shared webhook secret.
* @param int $current_time Current Unix timestamp.
* @param int $allowed_seconds Maximum accepted age in seconds.
* @return array Verification result.
*/
function ogSnippetWebhookReplayWindowCheck(string $body, string $timestamp, string $signature, string $secret_key, int $current_time, int $allowed_seconds): array {
$errors = array();
$timestamp_value = filter_var($timestamp, FILTER_VALIDATE_INT);
if ($timestamp_value === false) {
$errors[] = 'Webhook timestamp is invalid.';
} else {
$age = abs($current_time - (int) $timestamp_value);
if ($age > $allowed_seconds) {
$errors[] = 'Webhook timestamp is outside the replay window.';
}
}
$expected_signature = '';
if ($timestamp_value !== false) {
$signed_payload = (string) $timestamp_value.'.'.$body;
$expected_signature = hash_hmac('sha256', $signed_payload, $secret_key);
}
if ($expected_signature === '' || hash_equals($expected_signature, $signature) === false) {
$errors[] = 'Webhook signature is invalid.';
}
return array(
'valid' => count($errors) === 0,
'errors' => $errors
);
}
$webhook_body = '{"ship":"enterprise","event":"dock"}';
$webhook_time = (string) time();
$webhook_secret = 'enterprise-webhook-secret-change-me';
$webhook_signature = hash_hmac('sha256', $webhook_time.'.'.$webhook_body, $webhook_secret);
$webhook_check = ogSnippetWebhookReplayWindowCheck($webhook_body, $webhook_time, $webhook_signature, $webhook_secret, time(), 300);
if ($webhook_check['valid'] === true) {
echo 'Enterprise webhook accepted.';
} else {
echo 'Enterprise webhook rejected.';
}