Skip to main content
Every downstream webhook delivery includes an X-Steadpay-Signature header. Verify this signature before processing the payload to ensure the request is authentic and the body has not been tampered with.

Signature format

X-Steadpay-Signature: sha256=<hex-encoded-hmac>
The signature is HMAC-SHA256 of the raw request body, keyed with your per-tenant webhook signing secret.

Getting your signing secret

In the SteadPay dashboard, go to Settings → Webhooks. Your signing secret is displayed alongside your webhook URL.

Verification example

const crypto = require('crypto')

function verifySignature(rawBody, signatureHeader, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(expected)
  )
}

// Express — use express.raw() to preserve the raw body
app.post('/webhooks/steadpay', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-steadpay-signature']
  if (!verifySignature(req.body, sig, process.env.STEADPAY_WEBHOOK_SECRET)) {
    return res.status(400).send('Invalid signature')
  }

  const payload = JSON.parse(req.body)
  // process payload...
  res.sendStatus(200)
})

Important notes

  • Use the raw body — compute the HMAC over the raw request bytes before any JSON parsing. Parsing and re-serializing will change whitespace and may break the signature.
  • Use a constant-time comparison (timingSafeEqual / hmac.compare_digest / hmac.Equal) to prevent timing attacks.
  • No timestamp in the signature — SteadPay signs the body only, not a timestamp. Use occurred_at in the payload for event ordering if needed.