Ir al contenido

Webhook

El sistema de notificación por webhook le alerta en tiempo real de los cambios en el estado de un pago. Interpretarlos correctamente es crítico para evitar liberar producto sin confirmación o dejar a un cliente esperando.

  • URL del webhook: la configura en el dashboard, en la configuración del dispositivo (notification_url).
  • Nivel: los webhooks operan por dispositivo. Cada dispositivo puede tener su propia URL.
  • Método HTTP: POST.
  • Body: JSON.
POST /su/endpoint HTTP/1.1
Content-Type: application/json
X-NONCE: 1645634942
X-SIGNATURE: 395a6c0294f0896fcc0e5827e926e12308f4fdca5c18da69d3af6879e5c80e2d
  • X-NONCE: Unix timestamp en segundos en el momento en que B4bit Pay generó la firma.
  • X-SIGNATURE: hex(HMAC_SHA256(bytes.fromhex(secret_key), nonce_ascii + body_utf8)).
firma = hex(HMAC_SHA256(secret_hex → bytes, nonce + body))
  • La Secret Key del dispositivo está en el dashboard en formato hexadecimal; decodifíquela a bytes antes de usarla.
  • nonce es la cadena ASCII del timestamp ("1645634942", no el número entero).
  • body es el cuerpo crudo de la petición, sin parsear ni reformatear (sin trailing newline adicional).
  • La concatenación es nonce + body sin separadores.
{
"fiat_amount": 5.0,
"fiat_currency": "USD",
"status": "AC",
"crypto_amount": 0.06519511,
"unconfirmed_amount": 0.77,
"confirmed_amount": 0.0,
"currency": "DASH",
"identifier": "cc80e0b5-f779-4094-be65-fcee4b5bd041"
}
  • AC con safe=true → pago recibido, estable, puede liberar.
  • CO → pago completado definitivamente.
  • OC → Out of Condition: recibido menor que esperado (no libere).
  • EX, CA → expirado o cancelado.

Ver estados de pago para la lista completa.

import crypto from 'node:crypto';
export function verify(secretHex, nonce, bodyRaw, signatureHex) {
const key = Buffer.from(secretHex, 'hex');
const mac = crypto
.createHmac('sha256', key)
.update(nonce + bodyRaw, 'utf8')
.digest('hex');
const a = Buffer.from(mac);
const b = Buffer.from(signatureHex);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Use estos valores para verificar que su implementación produce la misma firma que B4bit Pay. Si obtiene un resultado distinto, revise encoding, trailing newline, decodificación del secret o concatenación.

secret_key (hex):
02d4b921007cad413e79731dd02b3267cd43a14d150a0ae6a1c651942122bb62
nonce:
1645634942
body (UTF-8 compacto, exactamente así):
{"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 esperado:
395a6c0294f0896fcc0e5827e926e12308f4fdca5c18da69d3af6879e5c80e2d

Ver self-checks en Node/Python/PHP/Go en Webhook — test vectors.

Su servidor debe responder HTTP 2xx (200/201/202/204) rápidamente — idealmente en menos de 5 segundos. Procese la lógica pesada de forma asíncrona.

Aunque el backend no reintenta, trate cada webhook como potencialmente repetido (por retries de red a nivel TCP, deployments del merchant con duplicación temporal, etc.). Desduplique por identifier + status + edited_at antes de aplicar side effects.