Aurax PayAurax Pay Docs

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
"token-keyword">const crypto = require("token-string">'crypto')

"token-keyword">function verifyAuraxSignature(rawBody, signatureHeader, secret) {
  "token-keyword">const expected = crypto
    .createHmac("token-string">'sha256', secret)
    .update(rawBody) // rawBody must be a Buffer, not parsed JSON
    .digest("token-string">'hex')

  // Use timingSafeEqual to prevent timing attacks
  "token-keyword">const a = Buffer."token-keyword">from(expected, "token-string">'utf8')
  "token-keyword">const b = Buffer."token-keyword">from(signatureHeader || "token-string">'', "token-string">'utf8')

  "token-keyword">if (a.length !== b.length) "token-keyword">return false
  "token-keyword">return crypto.timingSafeEqual(a, b)
}

// 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) => {
    "token-keyword">const sig = req.headers["token-string">'x-aurax-signature']
    "token-keyword">if (!verifyAuraxSignature(req.body, sig, process.env.AURAX_WEBHOOK_SECRET)) {
      "token-keyword">return res.status(400).send("token-string">'Bad signature')
    }
    "token-keyword">const event = JSON.parse(req.body)
    res.json({ received: true })
    // process event...
  }
)
Python / FastAPI
"token-keyword">import hmac
"token-keyword">import hashlib
"token-keyword">from fastapi "token-keyword">import Request, HTTPException

"token-keyword">async def verify_signature(request: Request) -> bool:
    raw_body = "token-keyword">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."token-keyword">new(secret, raw_body, hashlib.sha256).hexdigest()
    "token-keyword">return hmac.compare_digest(expected, signature)

@app.post("token-string">'/webhooks/aurax')
"token-keyword">async def handle_webhook(request: Request):
    "token-keyword">if not "token-keyword">await verify_signature(request):
        raise HTTPException(status_code=400, detail="token-string">'Invalid signature')

    event = "token-keyword">await request.json()
    # process event...
    "token-keyword">return { "token-string">'received': True }
PHP
"token-keyword">function verifyAuraxSignature(string $rawBody, string $signature, string $secret): bool {
    $expected = hash_hmac("token-string">'sha256', $rawBody, $secret);
    "token-keyword">return hash_equals($expected, $signature);
}

$rawBody = file_get_contents("token-string">'php://input');
$signature = $_SERVER["token-string">'HTTP_X_AURAX_SIGNATURE'] ?? "token-string">'';
$secret = getenv("token-string">'AURAX_WEBHOOK_SECRET');

"token-keyword">if (!verifyAuraxSignature($rawBody, $signature, $secret)) {
    http_response_code(400);
    exit("token-string">'Invalid signature');
}

$event = json_decode($rawBody, true);
// 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 200 fast enough — Aurax Pay will retry if no response in 10 seconds