Verifying Signatures
Every webhook delivery carries an X-RYNT-Signature header so you can confirm it genuinely came from RYNT and wasn't tampered with or replayed.
The header
X-RYNT-Signature: t=1786970433,v1=cfd2cf2e81e8660d4969b69ccdb2bb7827a8a6ce...
t— the Unix timestamp when the event was signed.v1—HMAC_SHA256(secret, "<t>.<rawBody>")in hex, wheresecretis the value returned when you registered the webhook.
Verify (Node.js)
const crypto = require('crypto');
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.` + rawBody)
.digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // 5 min
const match = crypto.timingSafeEqual(
Buffer.from(expected), Buffer.from(parts.v1)
);
return fresh && match;
}Verify (PHP)
function verify(string $rawBody, string $header, string $secret): bool {
parse_str(str_replace(',', '&', $header), $p); // t=…&v1=…
$expected = hash_hmac('sha256', $p['t'] . '.' . $rawBody, $secret);
$fresh = abs(time() - (int)$p['t']) < 300;
return $fresh && hash_equals($expected, $p['v1']);
}
Use the raw bodyCompute the HMAC over the exact bytes you received, before any JSON parse/re-serialize. Re-stringifying can change spacing and break the signature. Reject anything that doesn't match or whose timestamp is older than a few minutes.
Updated about 2 hours ago
Did this page help you?
