Integrate NirbhorPay into your application to accept payments via bKash, Nagad, and Rocket.
Every API key belongs to one of two environments. Use sandbox for development and testing — no real money moves. Switch to live when you are ready to accept real payments.
| Sandbox | Live | |
|---|---|---|
| Key prefix | up_test_sk_… | up_live_sk_… |
| Real money | No | Yes |
| Gateway calls | Simulated | Real (bKash / Nagad / Rocket / UPay) |
| Checkout UI | Shows simulate buttons | Shows real payment form |
| Where to create | Dashboard → API Keys → Sandbox tab | Dashboard → API Keys → Live tab |
Create a payment with a sandbox key exactly as you would in production. The checkout page will show Simulate Success and Simulate Failure buttons instead of a real gateway form.
POST /api/businesses/biz_abc/checkout
X-Nirbhorpay-Api-Key: up_test_sk_... ← sandbox key
{
"full_name": "Test User",
"amount": 100,
"redirect_url": "https://yoursite.com/payment/success"
}After the customer (or your automated test) clicks Simulate Success, the transaction moves to COMPLETED and NirbhorPay fires your webhook (if configured) with a synthetic transaction_id of the form SANDBOX_<timestamp>.
up_live_sk_…)redirect_url, cancel_url, and webhook_url to production URLsAll API requests must include your secret key in the following header:
X-Nirbhorpay-Api-Key: up_live_sk_... # or up_test_sk_... for sandbox
Each key carries explicit permissions. Attempting an action your key is not permitted for returns 403 API_KEY_FORBIDDEN. Exceeded rate limits return 429 RATE_LIMITED.
POST /checkout → receive payment_url and invoice_idpayment_urlredirect_url and/or webhook_urlPOST /verify server-side to confirm before fulfilling the orderInitialise a new payment session. Returns a hosted checkout URL to redirect your customer to. Requires the payment:create permission.
| Field | Type | Required | Description |
|---|---|---|---|
| full_name | string | Yes | Customer's full name (min 2 chars) |
| string | No | Customer's email address | |
| amount | number | Yes | Amount in BDT (must be > 0) |
| redirect_url | string (URL) | No | Where to send the customer after payment |
| cancel_url | string (URL) | No | Where to send the customer if they cancel |
| webhook_url | string (URL) | No | Per-payment webhook endpoint |
| return_type | "GET" | "POST" | No | How redirect_url is called. Default: "GET" |
| metadata | object | No | Arbitrary string key/value pairs stored with the transaction |
When return_type is "GET", NirbhorPay appends ?invoice_id=<id> to the redirect URL.
POST /api/businesses/biz_abc/checkout
X-Nirbhorpay-Api-Key: up_live_sk_... # use up_test_sk_... for sandbox
{
"full_name": "Rahim Uddin",
"email": "rahim@example.com",
"amount": 500,
"redirect_url": "https://yoursite.com/payment/success",
"cancel_url": "https://yoursite.com/payment/cancel",
"webhook_url": "https://yoursite.com/webhooks/payment",
"metadata": { "order_id": "ORD-9921" }
}{
"status": true,
"payment_url": "https://nirbhorpay.devbucket.co/pay/INV-ABC123",
"invoice_id": "INV-ABC123",
"webhook_signing_key": "a3f9..." // only present when webhook_url is supplied
}webhook_signing_key is returned only when you pass a webhook_url. Use it to verify the HMAC-SHA256 signature on incoming inline webhook deliveries (see Webhooks).
| Status | error | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Missing or invalid fields |
| 401 | MISSING_API_KEY | No API key header |
| 401 | INVALID_API_KEY | Key not found or revoked |
| 403 | API_KEY_FORBIDDEN | Key lacks payment:create permission |
| 429 | RATE_LIMITED | Too many requests |
Confirm the final status and details of a payment. Always call this server-side before fulfilling an order. Requires the payment:verify permission.
| Field | Type | Required | Description |
|---|---|---|---|
| invoice_id | string | Yes | The invoice_id returned from checkout |
POST /api/businesses/biz_abc/payments/verify
X-Nirbhorpay-Api-Key: up_live_sk_... # use up_test_sk_... for sandbox
{ "invoice_id": "INV-ABC123" }{
"status": true,
"full_name": "Rahim Uddin",
"email": "rahim@example.com",
"amount": "500.00",
"fee": "5.00",
"charged_amount": "505.00",
"invoice_id": "INV-ABC123",
"payment_method": "BKASH",
"sender_number": "01712345678",
"transaction_id": "TXN8273618",
"state": "COMPLETED"
}| Field | Description |
|---|---|
| status | true if payment completed, false otherwise |
| amount | Original amount in BDT |
| fee | Gateway fee deducted |
| charged_amount | Total charged to sender (amount + fee) |
| payment_method | Gateway used: BKASH, NAGAD, ROCKET, or UPAY |
| sender_number | Mobile number the payment was sent from |
| transaction_id | Gateway's own transaction reference |
| state | PENDING | COMPLETED | FAILED | CANCELLED |
| Status | error | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Missing invoice_id |
| 401 | MISSING_API_KEY | No API key header |
| 401 | INVALID_API_KEY | Key not found or revoked |
| 403 | API_KEY_FORBIDDEN | Key lacks payment:verify permission |
| 404 | NOT_FOUND | No transaction found for this invoice ID |
| 429 | RATE_LIMITED | Too many requests |
Refund a completed payment. The transaction status transitions to REFUNDED and a payment.refunded webhook event fires. Requires the payment:refund permission.
The id path parameter is the transaction id returned from the payments list or verify endpoints. Only COMPLETED payments can be refunded.
POST /api/businesses/biz_abc/payments/clx.../refund X-Nirbhorpay-Api-Key: up_live_sk_...
Returns the full serialized transaction with status: "REFUNDED".
| Status | error | Meaning |
|---|---|---|
| 400 | ONLY_COMPLETED_CAN_REFUND | Payment is not in COMPLETED state |
| 401 | MISSING_API_KEY | No API key header |
| 401 | INVALID_API_KEY | Key not found or revoked |
| 403 | API_KEY_FORBIDDEN | Key lacks payment:refund permission |
| 404 | TRANSACTION_NOT_FOUND | No transaction found for this id |
| 429 | RATE_LIMITED | Too many requests |
Payments expire if not completed within 15 minutes. An expired payment stays PENDING but cannot be completed — treat it as failed if expiresAt has passed.
Register webhook endpoints from your dashboard. NirbhorPay sends a POST request to your URL when a payment event fires. Failed deliveries are retried up to 5 times with exponential back-off starting at 1 minute.
| Event | Triggered when |
|---|---|
| payment.completed | Payment confirmed successfully |
| payment.failed | Payment attempt failed |
| payment.cancelled | Payment was cancelled |
| payment.refunded | Payment was refunded |
{
"event": "payment.completed",
"transaction": {
"id": "clx...",
"invoice_id": "INV-ABC123",
"amount": "500.00",
"status": "COMPLETED",
"customer_name": "Rahim Uddin",
"customer_email": "rahim@example.com",
"sender_number": "01712345678",
"gateway": "BKASH",
"transaction_id": "TXN8273618",
"created_at": "2026-04-10T10:00:00.000Z",
"updated_at": "2026-04-10T10:02:34.000Z"
}
}Every webhook request includes two headers:
| Header | Value |
|---|---|
| X-Nirbhorpay-Signature | HMAC-SHA256 of the raw request body, signed with your webhook secret |
| X-Nirbhorpay-Event | The event name, e.g. payment.completed |
// Node.js example
import { createHmac } from "crypto"
function isValidWebhook(rawBody, signature, secret) {
const expected = createHmac("sha256", secret)
.update(rawBody)
.digest("hex")
return expected === signature
}
// In your route handler:
const sig = req.headers["x-nirbhorpay-signature"]
if (!isValidWebhook(req.rawBody, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature")
}
// safe to process| Gateway | Default fee rate |
|---|---|
| bKash | 1.0% |
| Nagad | 1.0% |
| Rocket | 1.5% |
| UPay | configured per account |
Fee rates are configurable per gateway account in your dashboard. The defaults above apply unless overridden. Fees are reflected in fee and charged_amount on the verify response.
| Permission | What it allows |
|---|---|
| payment:create | Create new payment sessions |
| payment:verify | Verify payment status |
| payment:list | List transactions |
| payment:refund | Issue refunds |
| link:create | Create payment links |
| webhook:manage | Manage webhook endpoints |
All errors follow this shape:
{
"status": false,
"error": "ERROR_CODE",
"message": "Human-readable description (where provided)"
}Validation errors include a per-field issues array:
{
"status": false,
"error": "VALIDATION_ERROR",
"issues": [
{ "path": ["amount"], "message": "Amount must be greater than 0" }
]
}