NeratPay Docs

Webhooks

Events & signatures

Configure your HTTPS endpoint and event subscriptions in the dashboard. NeratPay signs the exact raw body with HMAC-SHA512.

Header

x-neratpay-signature — hex digest of HMAC-SHA512(rawBody, webhookSecret).

Read the raw bytes first, verify, then parse JSON. The API secret will not verify the signature — use the webhook secret from Developers.

Events

EventMeaning
payment.successfulDeposit succeeded (pair with credited)
virtualaccount.creditedSame deposit — credit your user once
virtualaccount.activeAccount number ready (Paystack/test; also sent for PalmPay)
transfer.completedPayout succeeded
transfer.failedPayout failed; amount available again
payment.failedReserved — a transfer that never arrives does not send this

Every event includes top-level livemode. It is false for test-mode events.

Example payload

{
  "event": "payment.successful",
  "livemode": true,
  "sentAt": "2026-10-18T12:00:00.000Z",
  "data": {
    "customerReference": "user-42",
    "transaction": {
      "id": "6712...",
      "reference": "palmpay-order-id",
      "amount": 2500,
      "fee": 0,
      "currency": "NGN",
      "status": "successful",
      "provider": "palmpay"
    }
  }
}

Delivery & retries

Tried once immediately. If your server does not answer 2xx within 8 seconds, NeratPay retries after 1 minute, 5 minutes, 30 minutes, and 2 hours, then stops. Answer 2xx after you have stored the event — including when you already processed that reference.

Verify (Node)

const crypto = require('crypto');

function verifyNeratPay(rawBody, signature, secret) {
  if (!signature || !secret) return false;
  const expected = crypto.createHmac('sha512', secret).update(rawBody).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(String(signature));
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

Verify (Python)

import hmac, hashlib

def verify_neratpay(raw_body: bytes, signature: str, secret: str) -> bool:
    if not signature or not secret:
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha512).hexdigest()
    return hmac.compare_digest(expected, signature)