Withdrawal lifecycle
A withdrawal request can be created only after the Merchant ID, registered website domain, API Key and Secret Key are verified against an active Merchant Panel. After authentication, the requested gross amount moves from available balance to pending balance and the API returns a unique pending transaction.
https://www.sendboxpay.cyouWithdrawal access requires an active Merchant Panel
Installing the package alone cannot submit requests. The configured Merchant ID, registered domain, API Key and Secret Key must match the active merchant account.
Use the standard signed request headers
Withdraw, status and balance endpoints require the same Merchant ID, registered domain, API Key, timestamp, nonce and HMAC-SHA256 signature format as the Deposit API.
| Header | Description | Required |
|---|---|---|
Content-Type | application/json | Yes |
X-SBP-Merchant-ID | The Merchant ID shown in the Merchant Panel. | Yes |
X-SBP-Merchant-Domain | The exact registered website domain without protocol or path. | Yes |
X-SBP-API-Key | Your active merchant API Key. | Yes |
X-SBP-Timestamp | Current Unix timestamp in seconds, within five minutes. | Yes |
X-SBP-Nonce | Unique 16–100 character request value. | Yes |
X-SBP-Signature | HMAC-SHA256 of {merchant_id}\n{merchant_domain}\n{timestamp}\n{nonce}\n{raw_json_body}. | Yes |
$merchantId = 'SENDBOX123456';
$merchantDomain = 'merchant.example.com';
$body = json_encode($payload, JSON_UNESCAPED_SLASHES);
$timestamp = (string) time();
$nonce = bin2hex(random_bytes(16));
$signature = hash_hmac(
'sha256',
$merchantId . "\n" . $merchantDomain . "\n" . $timestamp . "\n" . $nonce . "\n" . $body,
'YOUR_API_SECRET'
);
Submit a withdrawal request
https://www.sendboxpay.cyou/api/v1/withdraw/create.php| Field | Type | Required | Description |
|---|---|---|---|
order_id | string | Yes | Unique merchant reference, 6–80 letters, numbers, hyphens or underscores. |
amount | decimal | Yes | Gross amount with up to two decimal places. |
payment_method | string | Yes | BKASH, NAGAD, ROCKET or BANK. |
account_number | string | Yes | Recipient mobile wallet or bank account number, 6–50 characters. |
account_name | string | Yes | Recipient account holder name, 2–120 characters. |
callback_url | string | No | HTTPS endpoint for signed withdrawal events. Merchant profile URL is used when omitted. |
Request example
{
"order_id": "WD-10001",
"amount": "1000.00",
"payment_method": "BKASH",
"account_number": "01712345678",
"account_name": "Recipient Name",
"callback_url": "https://merchant.example.com/webhooks/sendboxpay"
}
Success response · HTTP 201
{
"success": true,
"order_id": "WD-10001",
"amount": "1000.00",
"charge": "15.00",
"net_amount": "985.00",
"status": "pending",
"message": "Withdrawal request created successfully."
}
The gross amount is deducted from available balance and added to pending balance when the request is accepted. Use a unique order_id to prevent duplicate submissions.
Query a withdrawal
https://www.sendboxpay.cyou/api/v1/withdraw/status.php{
"order_id": "WD-10001"
}
{
"success": true,
"withdrawal": {
"reference": "WD-10001",
"gross_amount": "1000.00",
"charge_amount": "15.00",
"net_amount": "985.00",
"status": "pending",
"description": "Withdrawal request created through API.",
"completed_at": null,
"created_at": "2026-07-16 14:40:00"
}
}
| Status | Meaning | Merchant action |
|---|---|---|
| pending | Request is being processed. | Do not submit the same order again. |
| success | Withdrawal completed. | Mark the recipient payout complete. |
| failed | Withdrawal failed. | Review the final balance and recipient details. |
| cancelled | Withdrawal was cancelled. | Do not treat it as paid. |
Read merchant balance
https://www.sendboxpay.cyou/api/v1/balance.phpSend an empty JSON object and sign the exact body {}.
{}
{
"success": true,
"balance": {
"available_balance": "15000.00",
"pending_balance": "1000.00",
"total_deposit": "22000.00",
"total_withdraw": "6000.00",
"updated_at": "2026-07-16 14:40:00"
}
}
Verify withdrawal events
After a withdrawal request is accepted, SendboxPay sends a signed withdrawal.created event to the configured callback URL. Verify the signature before storing the event.
| Header | Description |
|---|---|
X-SBP-Event | Event name, for example withdrawal.created. |
X-SBP-Timestamp | Unix timestamp used in the signature. |
X-SBP-Signature | HMAC-SHA256 of {timestamp}\n{raw_json_body} using the API Secret. |
{
"event": "withdrawal.created",
"sent_at": "2026-07-16T08:40:00+00:00",
"data": {
"order_id": "WD-10001",
"status": "pending",
"amount": "1000.00",
"charge": "15.00",
"net_amount": "985.00",
"currency": "BDT",
"payment_method": "bKash",
"account_number": "017****5678",
"account_name": "Recipient Name"
}
}
Node.js webhook verification
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.post('/webhooks/sendboxpay', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.header('X-SBP-Timestamp') || '';
const received = (req.header('X-SBP-Signature') || '').toLowerCase();
const rawBody = req.body.toString('utf8');
const expected = crypto
.createHmac('sha256', process.env.SENDBOXPAY_API_SECRET)
.update(`${timestamp}\n${rawBody}`)
.digest('hex');
if (!/^\d+$/.test(timestamp)
|| Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > 300
|| received.length !== expected.length
|| !crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(rawBody);
// Store and process the event idempotently.
return res.status(200).send('OK');
});
HTTP status and common failures
| HTTP | Meaning | Common cause |
|---|---|---|
201 | Created | Withdrawal accepted and marked pending. |
401 | Unauthorized | Invalid or disabled merchant credential, timestamp, nonce or signature. |
403 | Domain not active | The request domain does not match the domain registered for the Merchant ID. |
404 | Not found | Withdrawal order does not exist. |
409 | Conflict | Duplicate order ID or nonce. |
422 | Validation failed | Invalid recipient data, amount, callback URL or insufficient balance. |
{
"success": false,
"message": "Insufficient available balance."
}
Withdrawal protection requirements
Use server-to-server requests only
Withdrawal endpoints must never be called directly from a browser or mobile client.
Match the registered domain
Use the exact Merchant Panel domain in every signed request. A different domain is rejected before processing.
Generate unique order IDs
Persist the merchant reference before sending the request and never reuse it for another withdrawal.
Validate recipient details
Confirm account ownership and payment method before submitting a request.
Protect credentials
Store the API Secret in an encrypted environment and rotate it immediately if exposure is suspected.
Verify status independently
Use the status endpoint before displaying a final result, and process each final state only once.
Framework examples
<?php
$payload = [
'order_id' => 'WD-' . time(),
'amount' => '1000.00',
'payment_method' => 'BKASH',
'account_number' => '01712345678',
'account_name' => 'Recipient Name',
'callback_url' => 'https://merchant.example.com/webhooks/sendboxpay',
];
$merchantId = getenv('SENDBOXPAY_MERCHANT_ID');
$merchantDomain = getenv('SENDBOXPAY_MERCHANT_DOMAIN');
$body = json_encode($payload, JSON_UNESCAPED_SLASHES);
$timestamp = (string) time();
$nonce = bin2hex(random_bytes(16));
$signature = hash_hmac('sha256', $merchantId . "\n" . $merchantDomain . "\n" . $timestamp . "\n" . $nonce . "\n" . $body, getenv('SENDBOXPAY_API_SECRET'));
$ch = curl_init('https://www.sendboxpay.cyou/api/v1/withdraw/create.php');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-SBP-Merchant-ID: ' . $merchantId,
'X-SBP-Merchant-Domain: ' . $merchantDomain,
'X-SBP-API-Key: ' . getenv('SENDBOXPAY_API_KEY'),
'X-SBP-Timestamp: ' . $timestamp,
'X-SBP-Nonce: ' . $nonce,
'X-SBP-Signature: ' . $signature,
],
]);
$response = curl_exec($ch);
curl_close($ch);
$payload = [
'order_id' => 'WD-' . now()->timestamp,
'amount' => '1000.00',
'payment_method' => 'BKASH',
'account_number' => '01712345678',
'account_name' => 'Recipient Name',
];
$merchantId = config('services.sendboxpay.merchant_id');
$merchantDomain = config('services.sendboxpay.domain');
$body = json_encode($payload, JSON_UNESCAPED_SLASHES);
$timestamp = (string) time();
$nonce = Str::random(32);
$signature = hash_hmac('sha256', $merchantId . "\n" . $merchantDomain . "\n" . $timestamp . "\n" . $nonce . "\n" . $body, config('services.sendboxpay.secret'));
$response = Http::withBody($body, 'application/json')
->withHeaders([
'X-SBP-Merchant-ID' => $merchantId,
'X-SBP-Merchant-Domain' => $merchantDomain,
'X-SBP-API-Key' => config('services.sendboxpay.key'),
'X-SBP-Timestamp' => $timestamp,
'X-SBP-Nonce' => $nonce,
'X-SBP-Signature' => $signature,
])
->post('https://www.sendboxpay.cyou/api/v1/withdraw/create.php')
->throw()
->json();
import crypto from 'node:crypto';
const payload = {
order_id: `WD-${Date.now()}`,
amount: '1000.00',
payment_method: 'BKASH',
account_number: '01712345678',
account_name: 'Recipient Name'
};
const merchantId = process.env.SENDBOXPAY_MERCHANT_ID;
const merchantDomain = process.env.SENDBOXPAY_MERCHANT_DOMAIN;
const body = JSON.stringify(payload);
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(16).toString('hex');
const signature = crypto
.createHmac('sha256', process.env.SENDBOXPAY_API_SECRET)
.update(`${merchantId}\n${merchantDomain}\n${timestamp}\n${nonce}\n${body}`)
.digest('hex');
const response = await fetch('https://www.sendboxpay.cyou/api/v1/withdraw/create.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-SBP-Merchant-ID': merchantId,
'X-SBP-Merchant-Domain': merchantDomain,
'X-SBP-API-Key': process.env.SENDBOXPAY_API_KEY,
'X-SBP-Timestamp': timestamp,
'X-SBP-Nonce': nonce,
'X-SBP-Signature': signature
},
body
});
Download Withdraw Module
Includes complete integration documentation, installation guide, example project structure, sample configuration, API client examples, status queries and webhook verification for PHP, Laravel and Node.js.
