Webhook Signature Verifier
Verifies a signed webhook payload using a timestamp tolerance and shared secret.
Function signature
ogVerifyWebhookSignature(raw_body, signature, secret, timestamp = 0, tolerance_seconds = 300, algorithm = 'sha256')
Categories
- Security
Parameters
raw_bodyRaw request body exactly as received.signatureSignature header value.secretShared webhook secret.timestampOptional provider timestamp.tolerance_secondsAccepted clock-skew window when timestamp is supplied.algorithmHMAC algorithm.Return value
Short public-safe status message.
- valid
- algorithm
- timestamp_checked
Compatibility
Existing function name and call order preserved; metadata signature corrected to source.
Minimum PHP version: 7.4
Security notes
Validate request method, identity, permissions, and caller-owned allowlists before use; keep secrets and internal paths out of public output.
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.
*/
/**
* Verifies a signed webhook payload using a timestamp tolerance and shared secret.
*
* Primary use case: Payment, CRM, deployment, or SaaS webhooks.
* Typical inputs: raw body, signature header, timestamp, secret.
* Typical output: boolean validation result.
*
* Implementation note: Use hash_hmac and hash_equals; reject stale timestamps.
*
* @param string $raw_body Raw request body exactly as received.
* @param string $signature Signature header value.
* @param string $secret Shared webhook secret.
* @param int $timestamp Optional provider timestamp.
* @param int $tolerance_seconds Accepted clock-skew window when timestamp is supplied.
* @param string $algorithm HMAC algorithm.
* @return array Structured verification result.
*/
function ogVerifyWebhookSignature($raw_body, $signature, $secret, $timestamp = 0, $tolerance_seconds = 300, $algorithm = 'sha256') {
$result = array(
'success' => false,
'message' => '',
'data' => array('valid' => false)
);
$raw_body = (string)$raw_body;
$signature = trim((string)$signature);
$secret = (string)$secret;
$timestamp = (int)$timestamp;
$tolerance_seconds = (int)$tolerance_seconds;
$algorithm = strtolower(trim((string)$algorithm));
if (empty($signature) || empty($secret)) {
$result['message'] = 'Missing signature or secret.';
return $result;
}
if (!in_array($algorithm, hash_hmac_algos(), true)) {
$result['message'] = 'Unsupported signature algorithm.';
return $result;
}
if ($tolerance_seconds < 60) {
$tolerance_seconds = 60;
}
if ($timestamp > 0) {
$age = abs(time() - $timestamp);
if ($age > $tolerance_seconds) {
$result['message'] = 'Webhook timestamp is outside the accepted window.';
return $result;
}
}
$normalized_signature = $signature;
$prefix = $algorithm . '=';
if (strpos($normalized_signature, $prefix) === 0) {
$normalized_signature = substr($normalized_signature, strlen($prefix));
}
$base = $raw_body;
if ($timestamp > 0) {
$base = $timestamp . '.' . $raw_body;
}
$expected = hash_hmac($algorithm, $base, $secret);
if (!hash_equals($expected, $normalized_signature)) {
$result['message'] = 'Webhook signature mismatch.';
return $result;
}
$result['success'] = true;
$result['message'] = 'Webhook signature verified.';
$result['data'] = array(
'valid' => true,
'algorithm' => $algorithm,
'timestamp_checked' => ($timestamp > 0)
);
return $result;
}