Verify Signatures
Every webhook from Aurax Pay is signed with HMAC-SHA256 using your webhook secret. Always verify the signature before processing a webhook — this prevents attackers from sending fake events to your endpoint.
⚠️Never skip signature verification in production. Without it, anyone who discovers your webhook URL can trigger actions in your system.
Your webhook secret
Find your webhook secret in Business Settings → Webhooks in your dashboard. It looks like: whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Store it as an environment variable: AURAX_WEBHOOK_SECRET
Verification algorithm
Aurax Pay computes the signature as:
HMAC-SHA256(rawRequestBody, webhookSecret)
The result is hex-encoded and sent in the X-Aurax-Signature header.
Verification examples
Node.js
const crypto = require("token-string">'crypto') function verifyAuraxSignature(rawBody, signatureHeader, secret) { const expected = crypto .createHmac("token-string">'sha256', secret) .update(rawBody) "token-comment">// rawBody must be a Buffer, not parsed JSON .digest("token-string">'hex') "token-comment">// Use timingSafeEqual to prevent timing attacks const a = Buffer.from(expected, "token-string">'utf8') const b = Buffer.from(signatureHeader || "token-string">'', "token-string">'utf8') if (a.length !== b.length) return false return crypto.timingSafeEqual(a, b) } "token-comment">// In your Express route, use express.raw() to get the raw body: app.post("token-string">'/webhooks/aurax', express.raw({ type: "token-string">'application/json' }), (req, res) => { const sig = req.headers["token-string">'x-aurax-signature'] if (!verifyAuraxSignature(req.body, sig, process.env.AURAX_WEBHOOK_SECRET)) { return res.status(400).send("token-string">'Bad signature') } const event = JSON.parse(req.body) res.json({ received: true }) "token-comment">// process event... } )
Python / FastAPI
import hmac import hashlib from fastapi import Request, HTTPException async def verify_signature(request: Request) -> bool: raw_body = await request.body() signature = request.headers.get("token-string">'x-aurax-signature', "token-string">'') secret = os.environ["token-string">'AURAX_WEBHOOK_SECRET'].encode() expected = hmac.new(secret, raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature) @app.post("token-string">'/webhooks/aurax') async def handle_webhook(request: Request): if not await verify_signature(request): raise HTTPException(status_code=400, detail="token-string">'Invalid signature') event = await request.json() # process event... return { "token-string">'received': True }
PHP
function verifyAuraxSignature(string $rawBody, string $signature, string $secret): bool { $expected = hash_hmac("token-string">'sha256', $rawBody, $secret); return hash_equals($expected, $signature); } $rawBody = file_get_contents("token-string">'php:"token-comment">//input'); $signature = $_SERVER["token-string">'HTTP_X_AURAX_SIGNATURE'] ?? "token-string">''; $secret = getenv("token-string">'AURAX_WEBHOOK_SECRET'); if (!verifyAuraxSignature($rawBody, $signature, $secret)) { http_response_code(400); exit("token-string">'Invalid signature'); } $event = json_decode($rawBody, true); "token-comment">// process event... http_response_code(200); echo json_encode(["token-string">'received' => true]);
✅Use
timingSafeEqual (or equivalent) for comparison — regular string equality is vulnerable to timing attacks that can leak your secret.Common mistakes
- Parsing JSON before verifying — always verify against the raw bytes, not the parsed object
- Using string
===comparison instead of a constant-time function - Exposing your webhook secret in client-side code or logs
- Not returning
200fast enough — Aurax Pay will retry if no response in 10 seconds