Ir al contenido

Quickstart

Tiempo estimado: 5 minutos. Prerrequisitos: una cuenta de B4bit Pay y su API Key de dispositivo.

Ventana de terminal
export B4BIT_API_KEY=<tu-api-key> # de pay.b4bit.com/settings/commerce/devices
export B4BIT_SECRET=<tu-secret-key-hex> # solo para verificar webhook
Ventana de terminal
curl -H "X-Device-Id: $B4BIT_API_KEY" \
https://pos.b4bit.com/api/v1/currencies
Ventana de terminal
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/

La respuesta contiene identifier, web_url (pasarela hospedada) y, si pasó input_currency, también payment_uri, address, tag_memo y expected_input_amount.

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 s
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - Number(nonce)) > 20) return res.sendStatus(401);
// 2. firma HMAC-SHA256
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);
}
// 3. procesar
const payload = JSON.parse(body);
console.log('Webhook válido:', payload.identifier, payload.status);
res.sendStatus(200);
});
app.listen(3000);

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 mientras safe=false.
  • TAG/MEMO para XRP/XLM/ALGO — se muestra prominentemente al cliente.
  • reference — único por pedido, persistido junto al identifier de 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.