Baniq Pay — Developer API
Accept bKash / Nagad / Rocket / Upay on your own personal MFS numbers. Baniq auto-verifies each payment from the SMS your phone receives — no manual TrxID copy-paste — with a manual-TrxID fallback so nothing is ever lost.
নিজের নম্বরেই পেমেন্ট নিন, অর্ডার অটো-কনফার্ম। যেকোনো website বা app-এ integrate করুন — নিচের ৩টি ধাপ যথেষ্ট।
Overview
Buyer clicks "Pay"
│
▼ 1. Your server → POST /api/v1/orders (returns orderId + checkoutUrl)
▼ 2. Redirect buyer to checkoutUrl → Baniq hosted checkout (buyer pays)
│ auto-verified from the SMS
├─ 3a. signed webhook order.paid ───────► your server
└─ 3b. buyer redirected back to your successUrl → your server RE-VERIFIES, fulfils
?status=paid URL by hand.Base URL — your Baniq API origin. Production: https://api.baniq.app. All paths below are relative to it. The hosted checkout lives on https://pay.baniq.app.
Authentication
Create an API key in the dashboard → API & Webhook. Send both headers on every server-to-server call:
x-api-key-id: dpk_your_key_id x-api-secret: dps_your_key_secret
1 · Create an order
| Field | Type | Req | Notes |
|---|---|---|---|
amount | integer | yes | Paisa (BDT × 100). 82000 = ৳820.00. Fixed — buyers can't change it. Max ৳1,000,000. |
productName | string | yes | Shown on checkout. |
provider | string | — | bkash · nagad · rocket · upay. Omit to use your first active number. |
reference | string | — | Your own id, echoed back in verify + webhook. |
successUrl | string | — | Where to send the buyer after a successful payment. |
buyerEmail | string | — | ক্রেতার ইমেইল (আপনার চেকআউটে নেওয়া থাকলে) — পেমেন্টের পর Baniq-এর রসিদ/ডেলিভারি ইমেইল এই ঠিকানায় যাবে। |
customerName | string | — | ক্রেতার নাম (আপনার চেকআউট থেকে) — মার্চেন্টের অর্ডার তালিকায় দেখায়। |
customerPhone | string | — | ক্রেতার নিজের মোবাইল (01…) — ডেলিভারি SMS এই নম্বরেই যায়; না দিলে টাকা-পাঠানো নম্বরে। |
metadata.items | string[] | — | এক অর্ডারে একাধিক প্রোডাক্ট থাকলে প্রতিটার নাম — ডেলিভারি-ইমেইলের নিয়ম তখন প্রতিটা প্রোডাক্ট আলাদা করে মেলাবে। |
cancelUrl | string | — | Where to send the buyer if they cancel. |
metadata | any | — | Arbitrary JSON, echoed back in verify + webhook. |
Response
{
"orderId": "clx...",
"status": "created",
"amount": 82000,
"amountTaka": "820.00",
"currency": "BDT",
"checkoutUrl": "https://pay.baniq.app/checkout/clx...",
"expiresAt": "2026-08-16T10:20:00.000Z"
}
Redirect the buyer to checkoutUrl and store orderId against your order.
curl -X POST https://api.baniq.app/api/v1/orders \
-H 'x-api-key-id: dpk_KEY' -H 'x-api-secret: dps_SECRET' \
-H 'content-type: application/json' \
-d '{"amount":82000,"productName":"Order #1234","reference":"1234",
"successUrl":"https://mysite.com/thank-you?oid=1234"}'const res = await fetch('https://api.baniq.app/api/v1/orders', {
method: 'POST',
headers: {
'x-api-key-id': process.env.BANIQ_KEY_ID,
'x-api-secret': process.env.BANIQ_SECRET,
'content-type': 'application/json',
},
body: JSON.stringify({
amount: 82000, productName: 'Order #1234', reference: '1234',
successUrl: 'https://mysite.com/thank-you?oid=1234',
}),
});
const order = await res.json();
res.redirect(order.checkoutUrl); // send buyer to checkout$ch = curl_init('https://api.baniq.app/api/v1/orders');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'x-api-key-id: ' . getenv('BANIQ_KEY_ID'),
'x-api-secret: ' . getenv('BANIQ_SECRET'),
'content-type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 82000, 'productName' => 'Order #1234', 'reference' => '1234',
'successUrl' => 'https://mysite.com/thank-you.php?oid=1234',
]),
]);
$order = json_decode(curl_exec($ch), true);
header('Location: ' . $order['checkoutUrl']); exit;// composer require baniq/laravel-pay → see #laravel use Baniq\Pay\Facades\Baniq; $order = Baniq::createOrder([ 'amount' => 82000, // paisa (৳820) 'productName' => 'Order #1234', 'reference' => '1234', 'successUrl' => route('order.success', 1234), ]); return redirect($order['checkoutUrl']);
2 · Verify a payment (authoritative)
API-key authed, merchant-scoped. Call it from your thank-you page and/or on webhook receipt, before delivering goods.
{
"orderId": "clx...",
"status": "paid",
"paid": true,
"amount": 82000,
"amountTaka": "820.00",
"currency": "BDT",
"reference": "1234",
"metadata": { "orderId": 1234 },
"buyerSenderNumber": "01XXXXXXXXX",
"paidAt": "2026-08-16T10:14:00.000Z"
}
paid === true and that amount equals what you expected before fulfilling.3 · Webhook: order.paid
Set your Webhook URL + secret in the dashboard → API & Webhook. On a successful payment Baniq POSTs:
Headers
x-deshpay-event: order.paid x-deshpay-signature: sha256=<hex>
Body
{
"event": "order.paid",
"order": {
"id": "clx...",
"reference": "1234",
"metadata": { "orderId": 1234 },
"amount": 82000,
"amountTaka": "820.00",
"currency": "BDT",
"productName": "Order #1234",
"status": "paid",
"buyerSenderNumber": "01XXXXXXXXX",
"paidAt": "2026-08-16T10:14:00.000Z"
}
}
Verify the signature
The signature is sha256= + HMAC-SHA256 of the exact raw body using your webhook secret. Compare with a constant-time equality. Retries use exponential backoff — treat it idempotently (completing an already-paid order is a no-op). Respond 200 quickly.
import crypto from 'crypto';
// express.raw({ type: '*/*' }) so req.body is the exact bytes we signed
app.post('/webhooks/baniq', express.raw({ type: '*/*' }), (req, res) => {
const sig = req.get('x-deshpay-signature') || '';
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.BANIQ_WEBHOOK_SECRET)
.update(req.body).digest('hex');
const ok = sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!ok) return res.status(401).end('bad signature');
const { order } = JSON.parse(req.body.toString('utf8'));
markPaid(order.reference); // idempotent
res.status(200).end('ok');
});$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_DESHPAY_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $raw, getenv('BANIQ_WEBHOOK_SECRET'));
if (! hash_equals($expected, $sig)) { http_response_code(401); exit('bad signature'); }
$data = json_decode($raw, true);
markPaid($data['order']['reference']); // idempotent
http_response_code(200); echo 'ok';// routes/web.php — signature checked by the package middleware use Baniq\Pay\Http\Middleware\VerifyBaniqSignature; Route::post('/baniq/webhook', function (Request $request) { $data = $request->json()->all(); if (($data['event'] ?? null) === 'order.paid') { markPaid($data['order']['reference']); // idempotent } return response()->json(['ok' => true]); })->middleware(VerifyBaniqSignature::class); // exclude /baniq/webhook from CSRF protection
Order lifecycle
Happy path: created → awaiting_payment → auto_verifying → paid. Branches: manual_review (buyer paid from a different number, or timed out → TrxID fallback) and failed / expired. Your integration only needs paid (via verify/webhook) — Baniq handles the rest and surfaces manual review in the dashboard.
Errors & testing
Errors return a non-2xx status with { "message": "…" }. Common: 401 (bad/missing API key), 400 (invalid body, e.g. amount over the max), 404 (unknown order).
- Create an order → get redirected to checkout.
- Pay from the declared number → returned to your thank-you page, order verifies
paid. - Webhook received, signature valid, order marked paid idempotently.
- The amount you fulfil on always equals the verified
amount.
Laravel package
For Laravel apps, the baniq/laravel-pay package wraps everything above — a facade, config, and a webhook-signature middleware.
composer require baniq/laravel-pay php artisan vendor:publish --tag=baniq-config
Add credentials to .env:
BANIQ_API_BASE=https://api.baniq.app BANIQ_KEY_ID=your_key_id BANIQ_SECRET=your_secret BANIQ_WEBHOOK_SECRET=your_webhook_secret
Then create orders with the Baniq facade, protect your webhook route with VerifyBaniqSignature, and re-verify before fulfilling:
use Baniq\Pay\Facades\Baniq; // create + redirect $order = Baniq::createOrder(['amount' => 50000, 'productName' => 'Order #1234', 'reference' => '1234']); return redirect($order['checkoutUrl']); // authoritative check before delivering $v = Baniq::verifyOrder($order['orderId']); if ($v['paid'] === true && $v['amount'] === 50000) { /* fulfil */ }
integrations/laravel/README.md.Plugins (no code)
Using WooCommerce? Install the Baniq Pay plugin — it does all of the above for you. See integrations/woocommerce/. A Shopify Payments app is in progress.
Baniq Pay · Product · Dashboard · hello@baniq.app