Webhook Verification
Every webhook request from Radix includes an X-Radix-Signature header. Use this signature to confirm that the request genuinely came from Radix before processing it.
How it works
- Radix signs each webhook payload with your secret key using a SHA-256 HMAC
- The resulting hash is sent in the
X-Radix-Signaturerequest header - On your server, you recreate the hash using the same payload and secret key
- If the two hashes match, the request is authentic
The signature header
Every webhook request contains:
X-Radix-Signature: 081e703218bd09867a5b2bb4cf0d904dd16bbb53f49c442517bee3e68cec9bea
Verifying the signature
What to hash varies by webhook type
- Outward / card webhooks — hash the stringified
dataobject (body.data) - Inward transfer webhooks — hash the stringified full request body (
body)
- Node.js
- Python
const crypto = require("crypto");
function verifyWebhook(body, signature, secretKey) {
// For inward transfers, pass body directly.
// For outward/card webhooks, pass body.data instead.
const payload = JSON.stringify(body.data ?? body);
const hash = crypto
.createHmac("sha256", secretKey)
.update(payload)
.digest("hex");
return hash === signature;
}
// Usage in an Express handler
app.post("/webhook", (req, res) => {
const signature = req.headers["x-radix-signature"];
const isValid = verifyWebhook(req.body, signature, process.env.RADIX_SECRET);
if (!isValid) return res.status(401).send("Invalid signature");
// Safe to process req.body here
res.sendStatus(200);
});
import hmac
import hashlib
import json
def verify_webhook(body: dict, signature: str, secret_key: str) -> bool:
# For inward transfers, pass body directly.
# For outward/card webhooks, pass body["data"] instead.
payload = json.dumps(body.get("data", body), separators=(",", ":"))
hash = hmac.new(
secret_key.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(hash, signature)
Automatic notifications
info
On transaction completion, Radix automatically delivers a webhook to your registered endpoint for every successful or failed transaction — no polling required.
You do not need to query transaction status after initiating a transfer. Once your endpoint is registered, Radix pushes the result to you as soon as the transaction reaches a terminal state.