Webhooks

Receive real-time notifications when NadaPay events occur — transaction status changes, deposits, payouts, and verification updates — without polling.


Endpoints

MethodPathPurpose
POST/organization/webhooksRegister a webhook endpoint
GET/organization/webhooksList webhooks
GET/organization/webhooks/:idGet webhook details
PATCH/organization/webhooks/:idUpdate a webhook
DELETE/organization/webhooks/:idDelete a webhook
GET/organization/webhooks/deliveries/historyView delivery history
POST/organization/webhooks/deliveries/:id/retryRetry a failed delivery

Register a webhook

curl --request POST \
  --url $baseUrl/organization/webhooks \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Content-Type: application/json'

Webhook environment is determined by the API key used — np_test_... keys register sandbox webhooks, np_live_... keys register live webhooks.

Verify the signature

Every webhook includes an X-Nadapay-Signature header (HMAC-SHA256). Verify it before processing:

const crypto = require('crypto');

function verifyWebhook({ rawBody, signature, timestamp, secret }) {
  const signedPayload = `${timestamp}.${rawBody}`;

  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expectedSignature, 'hex')
  );
}

Verify the raw request body exactly as received. Parsing and re-stringifying the JSON before verification can change whitespace or key ordering and break the signature check.

Supported events

EventDescription
TRANSACTION.CREATEDA new transaction was initialized
TRANSACTION.AWAITING_FUNDSDeposit flow waiting for funds
TRANSACTION.PROCESSINGFunds detected, being processed
TRANSACTION.EXECUTINGSent to external banking/crypto rails
TRANSACTION.COMPLETEDSettled successfully
TRANSACTION.FAILEDFailed — check failureReason in payload
TRANSACTION.QUEUEDAwaiting liquidity resolution
VERIFICATION.COMPLETEDKYC/KYB process finished

Payload format

{
  "id": "delivery-uuid",
  "event": "TRANSACTION.COMPLETED",
  "environment": "LIVE",
  "timestamp": 1713289200000,
  "data": {
    "reference": "NP-ABCD-1234",
    "internalReference": "PAY-XYZ-999",
    "status": "COMPLETED",
    "sourceAmount": "1000.00",
    "sourceCurrency": "USD",
    "targetAmount": "1000.00",
    "targetCurrency": "USD",
    "totalFee": "5.00",
    "rail": "BRIDGE",
    "metadata": {}
  }
}

Amounts are dollar-style decimal strings, e.g. "1000.00".

Respond quickly

Return a 2xx as soon as you've received and validated the event — do the heavy processing in a background job.

HTTP/1.1 202 Accepted

Retry schedule

If your endpoint doesn't return 2xx, NadaPay retries with exponential backoff:

AttemptDelay
12 minutes
24 minutes
38 minutes
416 minutes
532 minutes

Best practices

  • Return 200/202 quickly; verify signature before processing
  • Deduplicate using the webhook id — deliveries can arrive more than once
  • Store secrets in a server-side secret manager
  • Use HTTPS on every endpoint
  • Log id, event, timestamp, and data.reference for reconciliation

Troubleshooting

  • Signature fails — confirm you're verifying the raw body, the secret matches, and you're reading from X-Nadapay-Signature
  • Repeated retries — your endpoint isn't returning 2xx
  • Duplicate events — use the webhook id as your dedup key


Did this page help you?