How to Integrate Payout API in Your Fintech App: Step-by-Step
Why Every Fintech App Needs a Payout API
Whether you are building a lending platform, neobank, insurance tech, or gig economy app, payout capabilities are a core requirement. Your users expect instant money transfers, and your operations demand automated disbursements.
This guide walks you through integrating the NxtBanking Payout API into your fintech application — from authentication to production deployment.
Prerequisites
- NxtBanking account with API access (sign up here)
- API Key and Secret from the dashboard
- Backend server (Node.js, Python, Java, PHP, or .NET)
- HTTPS endpoint for webhook callbacks
Step 1: Authentication
All API requests require Bearer token authentication. First, exchange your API key and secret for an access token:
POST /api/v1/auth/token
Content-Type: application/json
{
"api_key": "your_api_key",
"api_secret": "your_api_secret"
}
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 3600
}
Step 2: Register Beneficiary
Before sending a payout, register the beneficiary bank account. NxtBanking automatically performs penny-drop verification.
POST /api/v1/beneficiaries
Authorization: Bearer {access_token}
{
"name": "Vendor ABC Pvt Ltd",
"account_number": "1234567890",
"ifsc": "HDFC0001234",
"bank_name": "HDFC Bank",
"type": "current"
}
Step 3: Initiate Payout
POST /api/v1/payouts
Authorization: Bearer {access_token}
{
"beneficiary_id": "ben_abc123",
"amount": 50000,
"mode": "IMPS",
"reference_id": "ORDER_12345",
"narration": "Vendor payment for March 2026"
}
Step 4: Handle Webhooks
Set up your callback URL in the dashboard. NxtBanking sends real-time status updates:
POST /your-webhook-endpoint
{
"event": "payout.completed",
"payout_id": "pay_xyz789",
"reference_id": "ORDER_12345",
"status": "SUCCESS",
"utr": "1234567890",
"amount": 50000,
"timestamp": "2026-02-26T10:30:00Z"
}
Step 5: Error Handling Best Practices
- Idempotency: Always send a unique reference_id to prevent duplicate payouts.
- Retry Logic: Implement exponential backoff for transient failures (5xx errors).
- Balance Check: Always check your wallet balance before initiating payouts.
- Webhook Verification: Validate webhook signatures to prevent spoofing.
- Status Polling: If webhook is delayed, poll the payout status endpoint as fallback.
Step 6: Go Live Checklist
- Switch from sandbox to production API keys.
- Configure production webhook URL with HTTPS.
- Set up IP whitelisting for API access.
- Enable two-factor authentication on your NxtBanking account.
- Test with small amounts (₹1-10) before bulk payouts.
- Set up balance alert notifications.
Common Integration Patterns
Lending Platform
Loan approval → Trigger payout to borrower account → Track disbursement → Update loan record on webhook callback.
E-commerce Marketplace
Order delivered → Calculate seller settlement → Deduct commission → Trigger payout to seller → Auto-reconcile.
Gig Economy
Daily earnings calculated → Batch payout at end of day → Individual status tracking per worker → Handle failures with retry.
Frequently Asked Questions
How long does payout API integration take?
Most developers complete integration in 2-4 hours with NxtBanking documentation and sandbox environment.
Can I test without real money?
Yes. NxtBanking provides a full sandbox environment that simulates real payout behavior without actual fund transfers.
What programming languages are supported?
The RESTful API works with any language. We provide official SDKs for Node.js, Python, Java, PHP, and .NET.
📚 Payout API Content Hub
Production-Ready Implementation Walkthrough
The skeleton above shows the happy path, but shipping to production means handling the 20% of payouts that don’t complete on the first try. This section gives you the exact patterns we see work across our 400+ live integrations — job queues, idempotency, signed webhooks, and reconciliation.
1. Queue every payout — never call the API from a request handler
A payout API call can take 1–30 seconds on IMPS and up to 2 minutes on NEFT/RTGS. Never block the user’s HTTP request waiting for it. Push the job onto a queue (Bull, Sidekiq, Celery, AWS SQS — your choice) and return a payout_id immediately. The queue worker does the API call and writes the final status back to your database when the webhook arrives.
// Node.js — BullMQ worker
import { Queue, Worker } from "bullmq";
const payoutQueue = new Queue("payouts", { connection: redisConfig });
// API route — enqueue and return
app.post("/api/payouts", authMiddleware, async (req, res) => {
const job = await payoutQueue.add("push", {
userId: req.user.id,
beneficiaryId: req.body.beneficiary_id,
amount: req.body.amount,
referenceId: `PO_${Date.now()}_${req.user.id}`,
});
res.status(202).json({ payout_id: job.id, status: "QUEUED" });
});
// Worker — separate process
new Worker("payouts", async job => {
const token = await getToken(); // cached, reused
try {
const res = await pushPayout(token, job.data);
await db.payouts.update(job.data.referenceId, {
nxtb_payout_id: res.data.payout_id,
status: res.data.status,
});
} catch (err) {
if (err.response?.status === 409) {
// Duplicate — the original payout already exists. Look it up.
const existing = await lookupByReference(token, job.data.referenceId);
await db.payouts.update(job.data.referenceId, existing);
return;
}
throw err; // Bull retries with exponential backoff
}
}, { connection: redisConfig, concurrency: 10 });2. Idempotency with a client-side reference_id
The golden rule of payment integrations: generate a unique reference_id before you write to your own database. Store the reference alongside the pending payout row. If the API call times out or your worker crashes, the next attempt sends the same reference and our idempotency engine returns the original payout — no double-debit risk.
-- PostgreSQL schema for the local payout table
CREATE TABLE payouts (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
reference_id TEXT NOT NULL UNIQUE, -- your idempotency key
nxtb_payout_id TEXT, -- fills in after API call
beneficiary_id BIGINT NOT NULL REFERENCES beneficiaries(id),
amount NUMERIC(14, 2) NOT NULL,
currency CHAR(3) NOT NULL DEFAULT 'INR',
status TEXT NOT NULL DEFAULT 'QUEUED',
failure_reason TEXT,
utr TEXT,
fee NUMERIC(10, 2),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_payouts_status ON payouts(status) WHERE status IN ('QUEUED', 'PROCESSING');
CREATE INDEX idx_payouts_user ON payouts(user_id, created_at DESC);3. Webhook signature verification (do this first, always)
# Python / Flask
import hmac, hashlib, os
from flask import Flask, request, abort
SECRET = os.environ["NXTB_WEBHOOK_SECRET"].encode()
@app.post("/webhooks/nxtb/payout")
def payout_webhook():
sig = request.headers.get("X-NxtB-Signature", "")
expected = hmac.new(SECRET, request.get_data(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
event = request.get_json()
# Idempotent update — same webhook may arrive twice
db.execute("""
UPDATE payouts
SET status = %s, utr = %s, failure_reason = %s, updated_at = NOW()
WHERE reference_id = %s
AND status NOT IN ('SUCCESS', 'FAILED', 'REVERSED')
""", (event["status"], event.get("utr"), event.get("failure_reason"),
event["reference_id"]))
return "", 2004. Reconciliation — trust but verify
Webhooks can be lost. Networks split. Your app goes down at 3 AM. Never rely solely on webhooks to update payout state. Run a reconciliation job every 15 minutes that pulls every payout stuck in QUEUED or PROCESSING for more than 2 minutes and queries GET /v1/payouts/{id} directly.
// Node.js — cron job
import cron from "node-cron";
cron.schedule("*/15 * * * *", async () => {
const stale = await db.payouts.findMany({
where: {
status: { in: ["QUEUED", "PROCESSING"] },
updated_at: { lt: new Date(Date.now() - 2 * 60 * 1000) }
},
take: 200
});
const token = await getToken();
for (const p of stale) {
if (!p.nxtb_payout_id) continue; // never left our side
const live = await axios.get(
`https://api.nxtbanking.com/v1/payouts/${p.nxtb_payout_id}`,
{ headers: { Authorization: `Bearer ${token}` } }
);
if (live.data.status !== p.status) {
await db.payouts.update({
where: { id: p.id },
data: { status: live.data.status, utr: live.data.utr, updated_at: new Date() }
});
}
}
});5. User-facing status polling
Your React / Flutter / Android client shouldn’t poll the NxtBanking API directly — poll your own backend, which reads from your local payouts table. The table is always up to date (webhook or reconciliation job). This keeps your credentials server-side and gives you millisecond response times.
// React — useSWR hook with 2-second polling until terminal
import useSWR from "swr";
function usePayoutStatus(payoutId) {
const { data } = useSWR(
payoutId ? `/api/payouts/${payoutId}` : null,
fetcher,
{
refreshInterval: data =>
data?.status && ["SUCCESS", "FAILED", "REVERSED"].includes(data.status) ? 0 : 2000
}
);
return data;
}Pre-launch production checklist
- Secrets live in your environment manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault) — never in
.envfiles committed to git. - Webhook endpoint is HTTPS-only and verifies signatures before any parsing.
- Every payout row has a unique
reference_idpopulated before the API call. - Reconciliation cron runs at least every 15 minutes.
- Balance monitoring alerts when settlement account dips below 2× daily outflow.
- Rate-limiting: your app’s user-facing payout endpoint is capped per-user to prevent abuse.
- Double-entry accounting — every successful payout creates matching debit + credit rows in a ledger table.
- Operator dashboard shows stuck payouts (QUEUED / PROCESSING > 10 min) with a manual “re-check” button.
For scaling to multi-bank fallback (auto-failover between sponsor banks when one is down), see our follow-up article: Building a Multi-Bank Payout System. For the end-to-end API reference, start with Connected Banking Payout API Guide.





