Why Signature Verification Matters
Anyone can POST to your webhook endpoint. Without verification, a malicious actor could fake a deposit_detected event and trigger your order fulfillment flow without actually paying.
Paychainly signs every webhook with HMAC-SHA256 over a deterministic payload string. If the signature doesn't match, the request is forged.
The Signature Payload
The signed string is pipe-delimited and includes every field that matters for the payment:
event|txHash|fromAddress|toAddress|amount|blockNumber|timestamp|userIdThis covers the key fields — if any are tampered with, the signature breaks.
Verifying in Node.js
const crypto = require('crypto');
function verifyWebhook(secret, payload, signature) {
const str = [
payload.event,
payload.txHash,
payload.fromAddress,
payload.toAddress,
payload.amount,
payload.blockNumber,
payload.timestamp,
payload.userId,
].join('|');
const expected = crypto
.createHmac('sha256', secret)
.update(str)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex')
);
}Verifying in Python
import hmac, hashlib
def verify_webhook(secret, payload, signature):
msg = "|".join([
payload["event"], payload["txHash"],
payload["fromAddress"], payload["toAddress"],
payload["amount"], str(payload["blockNumber"]),
str(payload["timestamp"]), payload["userId"],
])
expected = hmac.new(secret.encode(), msg.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)Replay Attack Prevention
Store the txHash of every processed event in your database. Before fulfilling an order, check that this hash hasn't been seen before. Since txHash is unique per blockchain transaction, this prevents replays with zero extra cost.