Need a hand?

Setting up the panel or the Android app? The merchant guide walks through it.

Merchant Guide

Withdraw API Cash-out over USSD

আপনার approve করা হ্যান্ডসেট আপনার হয়ে USSD চালিয়ে টাকা পাঠাবে। আপনি শুধু বলে দেবেন কত টাকা, কোন operator-এ, আর কার নম্বরে — বাকিটা হ্যান্ডসেট করে।

Base URL https://cashier.nagorikpay.com
  • 01Request পাঠান
  • 02হ্যান্ডসেট USSD চালায়
  • 03SMS মিলে status ঠিক হয়
  • 04Webhook + API-তে ফলাফল
এই API আলাদা হোস্টে। Withdraw endpoint-গুলো https://cashier.nagorikpay.com-এ চলে, এই ডকুমেন্টেশনের সাইটে নয়। ভুল হোস্টে POST করলে 404 আসবে।

1. Authentication

প্রতিটি request-এ চারটা header লাগবে। Merchant panel → Withdraw → Withdraw API পেজে আপনার key ও secret আছে।

HeaderValue
X-API-Keyআপনার API key। Authorization: Bearer YOUR_KEY-ও চলবে।
X-TimestampUnix timestamp (seconds)। আমাদের ঘড়ির সঙ্গে ৩০০ সেকেন্ডের বেশি ফারাক চলবে না।
X-Nonceপ্রতি request-এ নতুন random string, ৮–৬৪ অক্ষর। একবার ব্যবহৃত nonce আর চলবে না।
X-Signatureনিচের নিয়মে বানানো HMAC-SHA256, lowercase hex।
Signing formula
HMAC-SHA256(
    key   = API_SECRET,
    message = X-Timestamp + "." + X-Nonce + "." + RAW_REQUEST_BODY
)
Body অবশ্যই হুবহু। Signature পুরো raw body-র উপর হয় — JSON আবার encode করলে বা field-এর ক্রম বদলালে signature মিলবে না। যে string পাঠাচ্ছেন, সেই string দিয়েই sign করুন।
PHP signer
$key    = 'YOUR_API_KEY';
$secret = 'YOUR_API_SECRET';
$url    = 'https://cashier.nagorikpay.com/api/v1/request';

$body = json_encode([
    'operator'  => 'bkash',
    'amount'    => 1000,
    'recipient' => '01700000000',
    'reference' => 'INV-1001',
], JSON_UNESCAPED_SLASHES);

$timestamp = (string) time();
$nonce     = bin2hex(random_bytes(8));

$signature = hash_hmac('sha256', $timestamp . '.' . $nonce . '.' . $body, $secret);

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $body,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'X-API-Key: '     . $key,
        'X-Timestamp: '   . $timestamp,
        'X-Nonce: '       . $nonce,
        'X-Signature: '   . $signature,
    ],
]);

$response = json_decode(curl_exec($ch), true);
curl_close($ch);

IP allow-list সেট করা থাকলে শুধু সেই IP/CIDR থেকেই call হবে। খালি রাখলে সব IP খোলা — কোনটা চালু আছে সেটা আপনার Withdraw API পেজে লেখা থাকে।

2. Request a withdraw

POST
https://cashier.nagorikpay.com/api/v1/request
FieldRequiredNote
operatorহ্যাঁbkash, nagad, rocket, upay, tap
amountহ্যাঁ০-এর বেশি। ৩ দশমিক স্থান পর্যন্ত রাখা হয়।
recipientহ্যাঁযে নম্বরে টাকা যাবে। ৩০ অক্ষরে কাটা হয়।
referenceনাআপনার নিজের reference — webhook-এ ফেরত আসে।
idempotency_keyনাএকই key আবার পাঠালে নতুন transaction হবে না, আগেরটাই ফেরত আসবে। সবসময় পাঠানোই ভালো।
callback_urlনাফলাফল জানানোর জন্য। অবশ্যই https:// হতে হবে।
success_keywordনাSMS-এ এই শব্দ থাকলে success ধরা হবে। না দিলে operator-এর ডিফল্ট শব্দ।
brand_idনাকোন brand-এর টাকা — হিসাবের জন্য।
নম্বর কখনো পাঠাবেন না। কে call করছে সেটা API key থেকেই বোঝা যায়, তাই request-এ কোনো uid নেই — জাল করার মতো কিছু নেই।
Response — accepted
{
  "status": "success",
  "message": "Request accepted.",
  "data": {
    "transaction_id": "WD202609220001",
    "reference": "INV-1001",
    "status": "pending",
    "operator": "bkash",
    "amount": 1000,
    "recipient": "01700000000",
    "smstranx": "",
    "error_message": "",
    "created_at": "2026-09-22 14:05:11",
    "completed_at": ""
  }
}

status প্রথমে pending থাকে — হ্যান্ডসেট কাজ শেষ করলে বদলাবে। ফলাফল পেতে webhook ব্যবহার করুন, অথবা transaction_id দিয়ে poll করুন।

3. Check a transaction

GET
https://cashier.nagorikpay.com/api/v1/transactions/{transaction_id}

Authentication header আগের মতোই — তবে GET-এ body নেই, তাই signature হয় timestamp + "." + nonce + "." + "" (খালি string)।

Request example
curl 'https://cashier.nagorikpay.com/api/v1/transactions/WD202609220001' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'X-Timestamp: 1789290000' \
  -H 'X-Nonce: 9f2c1ab34de5' \
  -H 'X-Signature: SIGNATURE_HEX'

Response ওই একই data shape-এ আসে। অন্য merchant-এর id আর না-থাকা id — দুটোতেই একই 404 No such transaction. আসে।

Statuses and errors

StatusMeaning
pendingগ্রহণ করা হয়েছে, হ্যান্ডসেট এখনো শুরু করেনি।
processingকোনো হ্যান্ডসেট নিয়ে USSD চালাচ্ছে।
successSMS মিলেছে, টাকা গেছে।
failedUSSD বা SMS বলছে হয়নি। error_message দেখুন।
cancelledবাতিল করা হয়েছে।
timeoutনির্ধারিত সময়ে SMS আসেনি।
HTTP errors
CodeWhen
401Key নেই / ভুল key, timestamp ৩০০ সেকেন্ডের বাইরে, nonce আগে ব্যবহৃত, signature মেলেনি।
403Key বন্ধ, account নিষ্ক্রিয়, withdraw access বন্ধ, অথবা IP allow-list-এ নেই।
422operator অজানা, amount শূন্য বা কম, recipient খালি।
429নির্ধারিত limit ছাড়িয়ে গেছে — কারণ message-এ লেখা থাকে।
503Withdraw system এখনো ইনস্টল হয়নি।

সবসময় {status, message, data?} shape-এ উত্তর আসে, পাশে আসল HTTP code-ও থাকে — যেকোনো একটা ধরে branch করতে পারেন।

Limits

Admin আপনার account-এ যত ceilings দিয়েছে, সেগুলো প্রতি request-এ যাচাই হয়। ছাড়িয়ে গেলে 429 আসে আর message-এ কারণ লেখা থাকে।

LimitWhat it caps
Per transactionএক request-এ সর্বোচ্চ কত টাকা।
Per dayএকদিনে মোট কত টাকা।
Transaction countdaily / monthly / total — কতগুলো transaction।
Devicesকতগুলো হ্যান্ডসেট approve করা যাবে।
Approve করা হ্যান্ডসেট না থাকলে request pending-এ পড়ে থাকবে — কেউ নিয়ে কাজ করবে না। আগে Withdraw → Devices থেকে হ্যান্ডসেট approve করে নিন।

Signed webhook

callback_url দিলে status বদলালেই সেখানে POST যাবে — JSON body, সঙ্গে signature header।

Headers
Content-Type: application/json
X-Timestamp: 1789290000
X-Nonce: 3f9c1ab34de5
X-Signature: SIGNATURE_HEX
X-Event: withdraw.success
Payload
{
  "event": "withdraw.success",
  "transaction_id": "WD202609220001",
  "reference": "INV-1001",
  "status": "success",
  "operator": "bkash",
  "amount": 1000,
  "recipient": "01700000000",
  "smstranx": "ABC123XYZ",
  "completed_at": "2026-09-22 14:06:02"
}

Signing key আপনার API secret। নিয়ম একই — timestamp + "." + nonce + "." + raw body, তবে এখানে nonce আমরাই বানাই এবং X-Nonce header-এ পাঠাই।

PHP verification
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
$nonce     = $_SERVER['HTTP_X_NONCE'] ?? '';
$given     = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$rawBody   = file_get_contents('php://input');

$expected = hash_hmac('sha256', $timestamp . '.' . $nonce . '.' . $rawBody, 'YOUR_API_SECRET');

// Signature সবসময় আসে — না এলে request আমাদের নয়।
if ($given === '' || abs(time() - (int) $timestamp) > 300 || !hash_equals($expected, $given)) {
    http_response_code(401);
    exit('Invalid signature');
}

$payload = json_decode($rawBody, true);
// Signature check-এর পরেই payment process করুন।
HTTPS বাধ্যতামূলক। http:// callback URL নেওয়া হয় না। আর একই transaction_id একবারই process করুন — আবার এলেই ignore।

Before you start

StepWhere
Withdraw access চালু করানAdmin → Withdraw Access
API key ও secret নিনWithdraw → Withdraw API
Callback URL ও IP allow-list বসানএকই পেজ
হ্যান্ডসেট approve করুনWithdraw → Devices
App ইনস্টল করে login করানফোনে, Devices পেজের username ও password দিয়ে
Withdraw access বন্ধ করলেই সাথে সাথে বন্ধ হয়ে যাবে — হ্যান্ডসেটও, API key-ও। কারণ access প্রতি call-এ আবার যাচাই হয়।