Verify a payment webhook signature
A payment webhook tells you an invoice settled — but only a verified one is safe to act on. Each delivery carries an X-Signature header holding an HMAC-SHA256 of the raw body. Recompute it with your signing secret and reject anything that does not match.
- Your webhook signing secret from the dashboard
- 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 signing secret and compare against X-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 settled events
Once the signature verifies, branch on status: pending, underpaid, overpaid or settled. Credit the order only on settled, and record the invoice id so a redelivery is a no-op.
常见问题
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.
继续阅读
充值、拿密钥、上线。
自助开通。支持加密货币或银行卡。按额度计费——重型原语更贵,简单调用很便宜。
获取 API 密钥