Skip to content

Webhook — test vectors

Use this vector to validate your verification function before connecting to production. If your implementation produces the expected signature, you can trust your algorithm.

Confirmed by the backend team. Applying hex(HMAC_SHA256(bytes.fromhex(secret), nonce + body)) on these values produces exactly the signature shown.

secret_key (hex):
02d4b921007cad413e79731dd02b3267cd43a14d150a0ae6a1c651942122bb62
nonce:
1645634942
body (UTF-8 compacto, exactamente así — sin espacios envolventes):
{"fiat_amount": 100.0, "fiat_currency": "USD", "status": "AC", "crypto_amount": 1.21461894, "unconfirmed_amount": 8.0, "confirmed_amount": 0.0, "currency": "DASH", "identifier": "1040095a-737d-41a2-a2e1-d031d19ec8cd"}
X-SIGNATURE:
395a6c0294f0896fcc0e5827e926e12308f4fdca5c18da69d3af6879e5c80e2d
import crypto from 'node:crypto';
const secretHex = '02d4b921007cad413e79731dd02b3267cd43a14d150a0ae6a1c651942122bb62';
const nonce = '1645634942';
const body = '{"fiat_amount": 100.0, "fiat_currency": "USD", "status": "AC", "crypto_amount": 1.21461894, "unconfirmed_amount": 8.0, "confirmed_amount": 0.0, "currency": "DASH", "identifier": "1040095a-737d-41a2-a2e1-d031d19ec8cd"}';
const expected = '395a6c0294f0896fcc0e5827e926e12308f4fdca5c18da69d3af6879e5c80e2d';
const got = crypto
.createHmac('sha256', Buffer.from(secretHex, 'hex'))
.update(nonce + body, 'utf8')
.digest('hex');
console.log(got === expected ? '✓ OK' : `✗ mismatch: ${got}`);

If your result does not match 395a6c02…, check in this order:

  1. Secret decoding — did you convert the hex to bytes? (Buffer.from(hex, 'hex') in Node, bytes.fromhex(hex) in Python, hex2bin($hex) in PHP).
  2. Body exact match — are you using the raw body, byte-for-byte? Re-serializing JSON changes the whitespace and breaks the signature. Read the body from the HTTP stream as a string/Buffer without passing it through JSON.parse followed by JSON.stringify.
  3. Body format — no surrounding spaces between { and the first field, with , and : between pairs.
  4. Concatenationnonce + body in that order, no separators or trailing newline.
  5. Charset — UTF-8.
  6. Output encoding — hex lowercase.
  • With Claude / Cursor: install the MCP server @b4bit/b4bit-pay-mcp (npx @b4bit/b4bit-pay-mcp). Its verify_webhook tool reproduces this algorithm with detailed diagnostics when the signature does not match (bad secret decoding, reversed concatenation, surrounding whitespace).
  • In production: capture a real webhook (using ngrok or cloudflared) and verify against that specific body byte-exact. This is the final validation before going live.