Quickstart
Estimated time: 5 minutes. Prerequisites: a B4bit Pay account and your device API key.
1. Set environment variables
Section titled “1. Set environment variables”export B4BIT_API_KEY=<your-api-key> # from pay.b4bit.com/settings/commerce/devicesexport B4BIT_SECRET=<your-secret-key-hex> # only used to verify webhook signatures2. List available currencies
Section titled “2. List available currencies”curl -H "X-Device-Id: $B4BIT_API_KEY" \https://pos.b4bit.com/api/v1/currenciesconst res = await fetch('https://pos.b4bit.com/api/v1/currencies', {headers: { 'X-Device-Id': process.env.B4BIT_API_KEY },});const currencies = await res.json();3. Create an order
Section titled “3. Create an order”curl -X POST \-H "X-Device-Id: $B4BIT_API_KEY" \-H "Content-Type: application/json" \-d '{ "expected_output_amount": 50.00, "fiat": "MXN", "notes": "Test order", "reference": "quickstart-'"$(date +%s)"'", "merchant_urlok": "https://example.com/pay/ok", "merchant_urlko": "https://example.com/pay/error"}' \https://pos.b4bit.com/api/v1/orders/const res = await fetch('https://pos.b4bit.com/api/v1/orders/', {method: 'POST',headers: { 'X-Device-Id': process.env.B4BIT_API_KEY, 'Content-Type': 'application/json',},body: JSON.stringify({ expected_output_amount: 50.00, fiat: 'MXN', notes: 'Test order', reference: 'quickstart-' + Date.now(), merchant_urlok: 'https://example.com/pay/ok', merchant_urlko: 'https://example.com/pay/error',}),});const order = await res.json();4. Receive and verify the webhook
Section titled “4. Receive and verify the webhook”import express from 'express';import crypto from 'node:crypto';
const app = express();app.use('/webhook', express.raw({ type: '*/*' }));
app.post('/webhook', (req, res) => {const nonce = req.header('x-nonce');const signature = req.header('x-signature');const body = req.body.toString('utf-8');
const now = Math.floor(Date.now() / 1000);if (Math.abs(now - Number(nonce)) > 20) return res.sendStatus(401);
const key = Buffer.from(process.env.B4BIT_SECRET, 'hex');const mac = crypto.createHmac('sha256', key).update(nonce + body).digest('hex');if (!crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(signature))) { return res.sendStatus(401);}
res.sendStatus(200);});
app.listen(3000);5. Download the full quickstart
Section titled “5. Download the full quickstart” REST / cURL Bash + openssl scripts.
Node / Express Express + crypto.
Python / Flask Flask + hmac.
PHP / Slim Slim + hash_hmac.
Go-Live checklist
Section titled “Go-Live checklist”- Timing-safe HMAC — uses
crypto.timingSafeEqual/hmac.compare_digest/hash_equals. Never==. - Nonce time window — rejects requests older than 20 s.
- Raw body — does not parse and reserialize JSON before signing/verifying.
- Fast HTTP 200 response — less than 5 seconds. Heavy logic in async queue.
- Idempotency — tolerates the same webhook arriving N times without duplicated side effects.
- All 11 statuses — each has a branch in your handler, even if only logging.
-
safefield — does not release product whilesafe=false. - TAG/MEMO for XRP/XLM/ALGO — displayed prominently to the customer.
-
reference— unique per order, persisted alongside the B4bit Payidentifier. - Logs without secrets — never logs
X-SIGNATURE,B4BIT_SECRET, or full webhook body. - Rate limits — implements exponential backoff on
HTTP 429. - Reconciliation — a periodic job calls
GET /orders/and crosses with your base in case a webhook was lost.