BaniqBaniqPAY
Baniq Pay Dashboard

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
Golden rule: never fulfil an order from the browser redirect alone. Always confirm with a server-to-server verify call or a signature-checked webhook — anyone can open a ?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
Keep the secret on your server. Never expose it in browser/client code. The secret is shown once at creation — store it safely.

1 · Create an order

POST /api/v1/orders
FieldTypeReqNotes
amountintegeryesPaisa (BDT × 100). 82000 = ৳820.00. Fixed — buyers can't change it. Max ৳1,000,000.
productNamestringyesShown on checkout.
providerstring—bkash · nagad · rocket · upay. Omit to use your first active number.
referencestring—Your own id, echoed back in verify + webhook.
successUrlstring—Where to send the buyer after a successful payment.
buyerEmailstring—ক্রেতার ইমেইল (আপনার চেকআউটে নেওয়া থাকলে) — পেমেন্টের পর Baniq-এর রসিদ/ডেলিভারি ইমেইল এই ঠিকানায় যাবে।
customerNamestring—ক্রেতার নাম (আপনার চেকআউট থেকে) — মার্চেন্টের অর্ডার তালিকায় দেখায়।
customerPhonestring—ক্রেতার নিজের মোবাইল (01…) — ডেলিভারি SMS এই নম্বরেই যায়; না দিলে টাকা-পাঠানো নম্বরে।
metadata.itemsstring[]—এক অর্ডারে একাধিক প্রোডাক্ট থাকলে প্রতিটার নাম — ডেলিভারি-ইমেইলের নিয়ম তখন প্রতিটা প্রোডাক্ট আলাদা করে মেলাবে।
cancelUrlstring—Where to send the buyer if they cancel.
metadataany—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)

GET /api/v1/orders/:orderId

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"
}
Check 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).

  1. Create an order → get redirected to checkout.
  2. Pay from the declared number → returned to your thank-you page, order verifies paid.
  3. Webhook received, signature valid, order marked paid idempotently.
  4. 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 */ }
Full setup — install, webhook route, CSRF exclusion, error handling — is in 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