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');