Webhooks events
Niftipay can send webhook events to your server whenever an order changes status (crypto invoices, fiat card orders, refunds, payouts, etc.). You can register one or multiple webhook URLs. Niftipay will POST JSON to each URL.What you need to build on your side
You must expose an HTTPS endpoint that accepts:- Method:
POST - Content-Type:
application/json - Path:
/niftipay/webhook(required)
Important: when you register a webhook URL in Niftipay, we force the path toYour endpoint should:/niftipay/webhook. So if you typehttps://example.com/hooks, the stored URL becomes:https://example.com/hooks/niftipay/webhook
- Verify the signature (recommended, see below)
- Parse the JSON body
- Return HTTP 2xx quickly (recommended: within 2–3 seconds)
- Process the event asynchronously (queue/job) to avoid timeouts and retries
Managing your webhook URLs (merchant API)
All webhook management is done via the authenticated merchant API.List webhooks
GET /api/user/webhooks
Returns your configured webhooks. We do not return secrets here.
Response
Create a webhook
POST /api/user/webhooks
Body
webhookSecret securely. You will not see it again unless you rotate.
Update a webhook URL
PUT /api/user/webhooks/:id
Body
Delete a webhook
DELETE /api/user/webhooks/:id
Response
Rotate a webhook secret
POST /api/user/webhooks/:id/secret
Rotates the secret and returns the new one once.
Response
Webhook payload format (common)
Niftipay sends a JSON object shaped like:Event names
You may receive theseevent values:
pendingpaidunderpaidcancelledexpiredrefundedpayout_upcomingpayout_sent
Most merchants only need to handle:paid,cancelled,refunded(+ optionallyunderpaid).
Signing & verification (recommended)
When a webhook has a secret, Niftipay signs the payload and includes:x-webhook-id: webhook id (orlegacy:<userId>for older single-webhook setups)x-timestamp: unix seconds (string)x-signature:v1=<hex_hmac_sha256>
payload = raw JSON string(exact bytes as sent)ts = x-timestampsigned = HMAC_SHA256(secret, ts + "." + payload)x-signature = "v1=" + hex(signed)
Node.js verification example
Python verification example
Replay protection (recommended)
Usex-timestamp to reject old requests. A typical window is 5 minutes:
abs(now - ts) <= 300
(event, order.id, txId) as idempotency keys (see below).
Delivery behavior, timeouts, and retries
Timeouts
Webhook delivery uses a short timeout (default ~6 seconds). If your endpoint is slow, the request may time out and be retried.Retries
Failed webhook deliveries may be retried (best-effort):- Retry interval: ~15 minutes
- Max attempts: 3
408,409,425,429- any
5xx
400,401,403,404,405,410,415,422
200 immediately.
Idempotency & processing recommendations
Webhook requests can be delivered more than once (network retries, timeouts, upstream duplicates). Best practice:- Deduplicate by a stable key:
- Crypto:
(event, order.id, txId)or justtxIdfor payments - Fiat:
(event, order.id, nopayn.order_id)and/or NoPayn refund id when present
- Crypto:
- Store “processed” markers in your DB
- Keep the webhook handler stateless and fast
- Always verify signatures in production
Crypto webhooks (orders paid on-chain)
Crypto payments are detected via the Tatum inbound webhook (/api/tatum/webhook), then Niftipay emits a merchant webhook event.
Crypto order events you’ll commonly see
pending
A crypto order was created and is waiting for payment.
paid
Payment was received and accepted (including under-payment within tolerance, if enabled on the platform).
underpaid
Payment was received but not enough to consider the order paid. You may receive multiple underpaid events as more funds arrive.
cancelled / expired
Order was cancelled or expired before completion.
refunded
Refund was executed from the deposit address to a refund address (for cancelled orders).
Example payloads (crypto)
paid example
underpaid example
refunded example (crypto refund endpoint)
Fiat webhooks
Fiat card payments are different than crypto payments so the shape will be different Niftipay emits a merchant webhook to your configured URL(s) with the sameevent model as crypto (pending, paid, cancelled, expired, refunded).
Fiat status mapping (simplified)
NoPayn order status → merchant webhook event:new/processing→pendingcompleted→paidcancelled→cancelledexpired→expired- refunded (refund detected) →
refunded
Your merchant webhook receives a normalized event.
Example payloads (fiat)
pending example
paid example
refunded example (fiat)
Note: fiat webhook payloads intentionally avoid exposing internal orderKey to merchants.
Recommended webhook implementation checklist
Security
- ✅ Use HTTPS only
- ✅ Verify
x-signature(HMAC) using your storedwebhookSecret - ✅ Reject old timestamps (suggested ±5 minutes)
- ✅ Keep secrets out of logs
Reliability
- ✅ Respond
200 OKquickly (do not do heavy work in-request) - ✅ Process events in a queue (Redis, SQS, database jobs, etc.)
- ✅ Deduplicate events with an idempotency key
- ✅ Make your handler idempotent (safe to run twice)
Correctness
- ✅ Treat webhooks as the source of truth for status transitions
- ✅ Use
paidto fulfill orders, andrefundedto reverse fulfillment - ✅ Handle
underpaidif you allow partial top-ups from customers
Monitoring
- ✅ Log: event, order id, timestamp, webhook id, and processing outcome
- ✅ Alert on repeated failures or signature mismatches
- ✅ Store raw webhook payloads for short-term debugging (redact secrets)
Common mistakes
- Returning non-2xx while you “already accepted” the event → causes retries
- Doing heavy DB work inside the HTTP request → timeouts & duplicate deliveries
- Not deduplicating → double-fulfillment
- Not verifying signatures → anyone can spoof events to your endpoint
- Registering a URL that doesn’t implement
/niftipay/webhook→ you’ll never receive events