Skip to content
← Back to Snippets
Code

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.';
}