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
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 200 fast enough — Aurax Pay will retry if no response in 10 seconds