Product: BabyFX Pay

Webhooks

Webhooks are how BabyFX Pay tells your server that something happened. Every event is signed with HMAC-SHA256 over the raw body using your webhook_secret.

Always verify the signature. Never trust the payload until you have checked x-babyfx-signature. The endpoint is public, so anyone can POST to it.

Event types

Event Triggered when Idempotency key
payment.detected Paying tx in a block invoice.id + ":detected"
payment.confirmed Confirmations pass threshold invoice.id + ":confirmed"
payment.swept Sweep tx confirmed invoice.id + ":swept"
payment.failed Sweep permanently failed invoice.id + ":failed"
payment.expired Invoice passed expiry unpaid invoice.id + ":expired"

Headers

Header Value
Content-Type application/json
x-babyfx-event event type, e.g. payment.detected
x-babyfx-signature t=<unix_ts>,v1=<hex_hmac>
x-babyfx-delivery UUID per attempt, useful for dedupe
User-Agent BabyFX-Pay-Webhooks/1.0

Payload shape (all events)

{
  "id": "evt_01HXY...",
  "type": "payment.confirmed",
  "created_at": "2026-06-22T12:55:31Z",
  "data": {
    "invoice_id": "inv_a1b2c3d4",
    "merchant_id": 1,
    "deposit_address": "0x8e74c9d4...",
    "baby_amount_wei": "4500000000000000000",
    "fiat_amount": "4.50",
    "fiat_currency": "USD",
    "tx_hash": "0x9f3a...",
    "confirmations": 12,
    "sweep_status": "PENDING"
  }
}

Verifying the signature

import crypto from "node:crypto";

export function verifyWebhook(rawBody, header, secret) {
  // header: "t=1719063331,v1=abcdef..."
  const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
  const expected = crypto.createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");
  // timing-safe compare
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(parts.v1, "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Why a timestamp in the signed string? Replay protection. Reject any signature whose t is more than 5 minutes away from your server clock.

Retry policy

Attempt Delay
1 immediate
2 +30s
3 +2m
4 +10m
5 +1h
6 +6h
7 +12h
8 +24h (final)

A delivery is marked delivered=TRUE only on a 2xx response. 4xx is treated as a permanent failure (your bug, we will not retry). 5xx and timeouts trigger the retry ladder.