Reference

Webhook signature verification

Every webhook delivery includes a Socialspool-Signature header that you verify using your webhook secret. This proves the request genuinely came from SocialSpool and has not been tampered with.

Signature format

The Socialspool-Signature header contains two comma-separated key-value pairs:

t=1717200000,v1=abc123def456...
  • t: the Unix timestamp of when the signature was generated.
  • v1: the HMAC-SHA256 signature hex digest.
Verification steps
  1. Extract t and v1 from the Socialspool-Signature header.
  2. Optionally check that t is within your tolerance window (recommended: 5 minutes) to prevent replay attacks.
  3. Construct the signed payload: ${t}.${rawBody} where rawBody is the exact request body as a UTF-8 string.
  4. Compute HMAC-SHA256 of the signed payload using your webhook secret as the key.
  5. Compare the computed digest to v1 using a constant-time comparison function.
Node.js example
const crypto = require("node:crypto")

function verifySocialSpoolSignature({
  rawBody,
  signatureHeader,
  secret,
}) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((part) => part.split("="))
  )
  const timestamp = parts.t
  const expected = parts.v1
  const signedPayload = `${timestamp}.${rawBody}`
  const actual = crypto
    .createHmac("sha256", secret)
    .update(signedPayload)
    .digest("hex")
  return crypto.timingSafeEqual(
    Buffer.from(actual),
    Buffer.from(expected)
  )
}
Webhook secret

Each webhook endpoint has its own secret, generated when the endpoint is created. The secret is shown once and stored as a SHA-256 hash. If you lose the secret, create a new webhook endpoint and update your consumer.