wbhk
PaymentStatus (integration-specific)
Configure this webhook against a specific Integration in your Business Portal.
HMAC signature verification
Every webhook request includes a super-signature header so you can verify it came from Super and hasn't been tampered with.
Header format
The header contains a timestamp and a signature, separated by a comma:
super-signature: t:1669219987926,v1:vCcZMqom...base64...
t:— Unix timestamp in milliseconds, generated when the request was signedv1:— HMAC-SHA256 signature, base64-encoded
Verifying a request
- Parse the header. Split on "," to get the timestamp and signature parts, then split each on ":" to extract the values.
- Build the signed message. Concatenate the timestamp and the raw request body, with no separator: "message = timestamp + raw_body"
- Use the raw bytes of the request body — do not re-serialize parsed JSON, as whitespace or key ordering changes will invalidate the signature.
- Compute the expected signature. Generate an HMAC-SHA256 of message using your webhook secret as the key, and base64-encode the result.
- Compare signatures. Use a constant-time comparison (e.g. crypto.timingSafeEqual in Node, hmac.compare_digest in Python) to avoid timing attacks. Reject the request if they don't match.
- Check the timestamp. Reject requests whose timestamp is more than 5 minutes old to prevent replay attacks.
Example (Node.js)
const crypto = require('crypto');
function verifySuperSignature(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(',').map((p) => { return p.split(':'); })
);
const timestamp = parts.t;
const signature = parts.v1;
if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) {
return false;
}
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}${rawBody}`)
.digest('base64');
const expectedBuf = Buffer.from(expected, 'base64');
const actualBuf = Buffer.from(signature, 'base64');
if (expectedBuf.length !== actualBuf.length) {
return false;
}
return crypto.timingSafeEqual(expectedBuf, actualBuf);
}
200Return a 200 status to indicate that the data was received successfully, any other response will be deemed a failure and retried
