Appearance
Verify webhook signatures
When a webhook subscription has a secret_key, Upsign sends header X-UpSign-Signature containing a hex-encoded HMAC-SHA256 of the exact JSON body bytes used for the request.
Implementation reference: WebhookDispatcher::sendFromLog (api/models/WebhookDispatcher.php).
Algorithm
- Build
$payloadDataexactly as PHP does (event+datakeys). $payloadJson = json_encode($payloadData, JSON_UNESCAPED_SLASHES)hash_hmac('sha256', $payloadJson, $secret_key)→ compare toX-UpSign-Signatureusing a constant-time string equals.
WARNING
Use the raw request body string from your HTTP framework as received before JSON pretty-printing or field reordering. Any change to serialization breaks the digest.
PHP (reference)
php
$received = $_SERVER['HTTP_X_UPSIGN_SIGNATURE'] ?? ''; // CGI/FPM style; may vary by host
$raw = file_get_contents('php://input'); // raw JSON bytes as Upsign POSTed them
$expected = hash_hmac('sha256', $raw, $secret_key);
hash_equals($expected, $received);Node.js (example)
js
import crypto from 'crypto';
function verify(rawBody, secret, signatureHex) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected, 'utf8'), Buffer.from(signatureHex, 'utf8'));
}Also validate X-UpSign-Event-ID against replay handling (store seen ids per your retention policy).