Skip to content

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

  1. Build $payloadData exactly as PHP does (event + data keys).
  2. $payloadJson = json_encode($payloadData, JSON_UNESCAPED_SLASHES)
  3. hash_hmac('sha256', $payloadJson, $secret_key) → compare to X-UpSign-Signature using 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).