Quickstart
Tiempo estimado: 5 minutos. Prerrequisitos: una cuenta de B4bit Pay y su API Key de dispositivo.
1. Configurar variables de entorno
Sección titulada «1. Configurar variables de entorno»export B4BIT_API_KEY=<tu-api-key> # de pay.b4bit.com/settings/commerce/devicesexport B4BIT_SECRET=<tu-secret-key-hex> # solo para verificar webhook2. Listar monedas disponibles
Sección titulada «2. Listar monedas disponibles»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();import os, requestsr = requests.get('https://pos.b4bit.com/api/v1/currencies', headers={'X-Device-Id': os.environ['B4BIT_API_KEY']})currencies = r.json()3. Crear una orden
Sección titulada «3. Crear una orden»curl -X POST \-H "X-Device-Id: $B4BIT_API_KEY" \-H "Content-Type: application/json" \-d '{ "expected_output_amount": 50.00, "fiat": "MXN", "notes": "Pedido de prueba", "reference": "quickstart-'"$(date +%s)"'", "merchant_urlok": "https://example.com/pago/ok", "merchant_urlko": "https://example.com/pago/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: 'Pedido de prueba', reference: 'quickstart-' + Date.now(), merchant_urlok: 'https://example.com/pago/ok', merchant_urlko: 'https://example.com/pago/error',}),});const order = await res.json();console.log('Web URL:', order.web_url);import os, requests, time
r = requests.post( 'https://pos.b4bit.com/api/v1/orders/', headers={'X-Device-Id': os.environ['B4BIT_API_KEY']}, json={ 'expected_output_amount': 50.00, 'fiat': 'MXN', 'notes': 'Pedido de prueba', 'reference': f'quickstart-{int(time.time())}', 'merchant_urlok': 'https://example.com/pago/ok', 'merchant_urlko': 'https://example.com/pago/error', },)order = r.json()La respuesta contiene identifier, web_url (pasarela hospedada) y, si pasó input_currency, también payment_uri, address, tag_memo y expected_input_amount.
4. Recibir y verificar webhook
Sección titulada «4. Recibir y verificar webhook»import express from 'express';import crypto from 'node:crypto';
const app = express();app.use('/webhook', express.raw({ type: '*/*' })); // body crudo
app.post('/webhook', (req, res) => {const nonce = req.header('x-nonce');const signature = req.header('x-signature');const body = req.body.toString('utf-8');
// 1. ventana temporal 15-20 sconst now = Math.floor(Date.now() / 1000);if (Math.abs(now - Number(nonce)) > 20) return res.sendStatus(401);
// 2. firma HMAC-SHA256const 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);}
// 3. procesarconst payload = JSON.parse(body);console.log('Webhook válido:', payload.identifier, payload.status);res.sendStatus(200);});
app.listen(3000);import os, hmac, hashlib, timefrom flask import Flask, request
app = Flask(__name__)
@app.post('/webhook')def webhook(): nonce = request.headers.get('X-NONCE', '') signature = request.headers.get('X-SIGNATURE', '') body = request.get_data(as_text=True)
# ventana temporal if abs(int(time.time()) - int(nonce)) > 20: return '', 401
# firma key = bytes.fromhex(os.environ['B4BIT_SECRET']) mac = hmac.new(key, (nonce + body).encode('utf-8'), hashlib.sha256).hexdigest() if not hmac.compare_digest(mac, signature): return '', 401
return '', 2005. Descargue el quickstart completo
Sección titulada «5. Descargue el quickstart completo» REST / cURL Scripts bash + openssl.
Node / Express Express + crypto.
Python / Flask Flask + hmac.
PHP / Slim Slim + hash_hmac.
Go-Live checklist
Sección titulada «Go-Live checklist»Antes de pasar a producción, confirme cada ítem:
- HMAC timing-safe — usa
crypto.timingSafeEqual/hmac.compare_digest/hash_equals. Nunca==. - Ventana temporal del nonce — rechaza peticiones con más de 20 s de diferencia.
- Body crudo — no parsea y reserializa JSON antes de firmar/verificar.
- Respuesta HTTP 200 rápida — menos de 5 segundos. Lógica pesada en cola asíncrona.
- Idempotencia — tolera el mismo webhook llegando N veces sin ejecutar side effects duplicados.
- Todos los 11 estados — cada uno tiene un branch en su handler, aunque sea para loguear.
- Campo
safe— no libera producto mientrassafe=false. - TAG/MEMO para XRP/XLM/ALGO — se muestra prominentemente al cliente.
-
reference— único por pedido, persistido junto alidentifierde B4bit Pay. - Logs sin secretos — nunca loguea
X-SIGNATURE,B4BIT_SECRET, ni body completo. - Rate limits — implementa backoff exponencial ante
HTTP 429. - Reconciliación — un job periódico que llama a
GET /orders/y cruza con su base por si perdió algún webhook.