Payments

Verify a payment webhook signature

A payment webhook tells you a charge was paid — but only a verified one is safe to act on. Each delivery carries an X-1stNode-Signature header holding sha256=<hex HMAC-SHA256 of the raw body>. Recompute it with the webhook secret returned from PUT /v1/payments/xpub and reject anything that does not match.

Antes de começar
  • The webhook secret returned by PUT or GET /v1/payments/xpub
  • Access to the raw, unparsed request body

1Read the raw body and signature header

Compute the HMAC over the exact bytes you received. Parsing to JSON and re-serializing can reorder keys and break the digest, so capture the raw body before any middleware touches it.

> POST /webhooks/1st-node
> X-Signature: 4f1c...9ab2
> { "invoice": "in_8fK2", "status": "settled", "amount_usd": 49 }

2Recompute the HMAC and compare

HMAC-SHA256 the raw body with your webhook secret, prefix it with sha256= and compare against X-1stNode-Signature in constant time. On any mismatch, return 400 and drop the event — do not credit the order.

const h = crypto.createHmac("sha256", SIGNING_SECRET)
  .update(rawBody).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(h),
    Buffer.from(req.header("X-Signature")))) return res.status(400).end();

3Act only on verified charge.paid events

Once the signature verifies, check event === 'charge.paid' and status === 'paid', then credit the order. Record the charge id so a redelivery is a no-op — delivery is best-effort and may retry.

Perguntas frequentes

Why must I use the raw body, not the parsed JSON?

Re-serializing JSON can change whitespace and key order, which changes the digest and makes a valid signature fail. Always HMAC the exact bytes received.

What happens if the signature does not match?

Reject it — return 4xx and ignore the payload. A mismatch means the body was altered or the sender lacks your secret, so it must never credit an order.

Continue lendo

Recarregue, pegue a chave e publique.

Autoatendimento. Pague em cripto ou cartão. Medido por créditos: primitivas pesadas custam mais, as simples são baratas.

Obter chave de API