Aurax PayAurax Pay Docs
Webhooks

Webhooks

Webhooks are HTTP callbacks Aurax Pay sends to your server when a payment completes, fails, or changes state. Use them together with polling for reliable payment confirmation.

๐Ÿ’กRegister your endpoint via the APIs & Secrets page in your dashboard, or call POST /merchant/webhooks with your JWT token. The signing secret is shown only once in the response โ€” copy it immediately and store as AURAXPAY_WEBHOOK_SECRET.

Register an endpoint

POST /merchant/webhooks
curl -X POST https://api.auraxpay.net/merchant/webhooks \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -d '{
    "url": "https://yourserver.com/webhooks/aurax",
    "events": ["payment.completed", "payment.failed", "payment.pending"]
  }'
Response -- secret shown once
{
  "success": true,
  "endpoint": {
    "id": "wh_xxxxxxxxxxxxxxxx",
    "url": "https://yourserver.com/webhooks/aurax",
    "events": ["payment.completed", "payment.failed", "payment.pending"],
    "isActive": true,
    "secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  },
  "message": "Save this secret now -- it will never be shown again."
}

Webhooks and polling

Webhook delivery is best-effort. Always implement polling as a fallback โ€” payments typically complete within 30 seconds:

MethodSpeedUse for
WebhookVaries, within 30sPrimary โ€” background fulfillment
Polling30s maxFallback โ€” user-facing status updates
Polling fallback -- Node.js
"token-keyword">async "token-keyword">function waitForPayment(reference, timeoutMs = 60000) {
  "token-keyword">const start = Date.now()
  while (Date.now() - start < timeoutMs) {
    "token-keyword">const res = "token-keyword">await fetch("token-string">'https://api.auraxpay.net/v1/payments/' + reference, {
      headers: { "token-string">'x-api-key': process.env.AURAXPAY_API_KEY },
    })
    "token-keyword">const { transaction } = "token-keyword">await res.json()
    "token-keyword">if (transaction.status === "token-string">'COMPLETED') "token-keyword">return transaction
    "token-keyword">if (transaction.status === "token-string">'FAILED') "token-keyword">throw "token-keyword">new Error("token-string">'Payment failed')
    "token-keyword">await "token-keyword">new Promise(r => setTimeout(r, 3000)) // poll every 3s
  }
  "token-keyword">throw "token-keyword">new Error("token-string">'Payment timed out')
}

How delivery works

1. Customer approves USSD prompt on their phone
2. Mobile network confirms payment to Aurax Pay
3. Aurax Pay sends a POST to your registered URL with X-Aurax-Signature
4. Your server verifies the signature
5. Your server responds with 200 OK within 10s

Delivery and retries

Aurax Pay expects a 2xx response within 10 seconds. If your endpoint fails or times out, we retry with exponential backoff:

AttemptDelay
1st retry30 seconds
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry12 hours

Webhook headers

Every webhook request includes these headers:

HeaderDescription
X-Aurax-SignatureHMAC-SHA256 signature of the raw request body. Use this to verify authenticity.
X-Aurax-EventThe event type. E.g. payment.completed
X-Aurax-DeliveryUnique ID for this delivery attempt. Useful for deduplication.
Content-TypeAlways application/json

Respond quickly, process async

Return a 200 response immediately, then process the webhook in the background. If your handler exceeds 10 seconds, Aurax Pay considers the delivery failed and retries.

Node.js / Express โ€” recommended pattern
app.post("token-string">'/webhooks/aurax', express.raw({ type: "token-string">'application/json' }), "token-keyword">async (req, res) => {
  // 1. Verify signature FIRST โ€” reject anything unsigned
  "token-keyword">const signature = req.headers["token-string">'x-aurax-signature']
  "token-keyword">const isValid = verifyAuraxSignature(req.body, signature, process.env.AURAX_WEBHOOK_SECRET)
  "token-keyword">if (!isValid) "token-keyword">return res.status(400).json({ error: "token-string">'Invalid signature' })

  // 2. Respond 200 immediately
  res.status(200).json({ received: true })

  // 3. Process async โ€” outside the request cycle
  "token-keyword">const event = JSON.parse(req.body)
  "token-keyword">await processWebhookEvent(event)
})

"token-keyword">async "token-keyword">function processWebhookEvent(event) {
  "token-keyword">switch (event.event) {
    "token-keyword">case "token-string">'payment.completed':
      "token-keyword">await fulfillOrder(event.transaction.metadata.orderId)
      "token-keyword">break
    "token-keyword">case "token-string">'payment.failed':
      "token-keyword">await notifyCustomer(event.transaction)
      "token-keyword">break
    "token-keyword">case "token-string">'payout.completed':
      "token-keyword">await markPayoutSent(event.transaction.id)
      "token-keyword">break
  }
}