Webhook Authentication

Every request sent by Zafepay to your webhook URL includes a signature header that allows you to verify the request is authentic and that the payload has not been tampered with.

Signature Header

X-Zafepay-Signature: sha256=

The signature is computed using HMAC-SHA256 over the raw request body, using the webhook's secret as the key. The secret is provided once when the webhook is created and is not retrievable afterwards.

Important: Store your webhook secret securely. If it is lost, you will need to delete and recreate the webhook to obtain a new one.


Verification Steps

  1. Read the raw request body as bytes — do not parse or re-serialize it first.
  2. Compute HMAC-SHA256(secret, raw_body) and hex-encode the result.
  3. Prepend sha256= to the result.
  4. Compare it to the value in the X-Zafepay-Signature header using a constant-time comparison to prevent timing attacks.
  5. If they match, the request is authentic. If not, reject it with a 401 or 403.

Code Examples

Important Notes

  • Always use the raw request body for verification. Parsing the JSON and re-serializing it may produce a different byte sequence and cause verification to fail.
  • Always use a constant-time comparison (timingSafeEqual, compare_digest, hash_equals). A regular string equality check (==) is vulnerable to timing attacks.
const crypto = require('crypto');

function verifyWebhook(rawBody, secret, signatureHeader) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}
import hmac
import hashlib

def verify_webhook(raw_body: bytes, secret: str, signature_header: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)
function verifyWebhook(string $rawBody, string $secret, string $signatureHeader): bool {
    $expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
    return hash_equals($expected, $signatureHeader);
}