Security

To ensure the integrity and authenticity of the data, BMG Money signs every webhook delivery with HMAC-SHA256. You should validate the signature on every incoming request before trusting its contents.

Signature headers

Header Description
X-Signature HMAC of the raw request body, encoded as lowercase hexadecimal (64 characters for SHA-256).
X-Signature-Algorithm Identifies the signing algorithm. Deliveries are signed with HMAC-SHA256.

The signature is computed over the exact raw body of the request — there is no timestamp and no prefix. The key is your subscription secret.

X-Signature == hex_lower( HMAC_SHA256( secret, raw_request_body ) )

Validate against the raw bytes. Compute the HMAC over the body exactly as received, before any JSON parser re-serializes it — re-encoded JSON can differ byte-for-byte and cause the comparison to fail. Always use a constant-time comparison.

Validation (C#)

using System.Security.Cryptography;
using System.Text;

// rawBody: the exact request body string as received
// signatureHeader: value of the "X-Signature" header
// secret: your subscription secret

using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody));
string computedSignature = Convert.ToHexString(hash).ToLowerInvariant();

bool isAuthentic = CryptographicOperations.FixedTimeEquals(
    Encoding.UTF8.GetBytes(computedSignature),
    Encoding.UTF8.GetBytes(signatureHeader));

if (!isAuthentic) throw new UnauthorizedAccessException();

Validation (Node.js)

const crypto = require('crypto');

// rawBody: the exact request body as received (Buffer or string)
// signatureHeader: value of the "X-Signature" header
// secret: your subscription secret

const computed = crypto
  .createHmac('sha256', secret)
  .update(rawBody, 'utf8')
  .digest('hex');

const isAuthentic = crypto.timingSafeEqual(
  Buffer.from(computed),
  Buffer.from(signatureHeader));

if (!isAuthentic) throw new Error('Invalid signature');