Sent when a customer payment method is disabled

Configure this webhook 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 signed
  • v1: — HMAC-SHA256 signature, base64-encoded

Verifying a request

  1. Parse the header. Split on "," to get the timestamp and signature parts, then split each on ":" to extract the values.
  2. Build the signed message. Concatenate the timestamp and the raw request body, with no separator: "message = timestamp + raw_body"
  3. Use the raw bytes of the request body — do not re-serialize parsed JSON, as whitespace or key ordering changes will invalidate the signature.
  4. Compute the expected signature. Generate an HMAC-SHA256 of message using your webhook secret as the key, and base64-encode the result.
  5. 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.
  6. 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);
}
Payload
const
enum
required
Allowed:
string
required
date-time
required
^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
data
object
required
Response
200

Return a 200 status to indicate that the data was received successfully

LoadingLoading…