Documentation/Withdraw Integration
WITHDRAW API · VERSION 1

Secure withdrawal integration

ProtocolHTTPS
FormatJSON
AuthenticationHMAC-SHA256
CurrencyBDT
01 OVERVIEW

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.

BASE URLhttps://www.sendboxpay.cyou
MERCHANT ACTIVATION

Withdrawal 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.

1Create Merchant PanelRegister the website domain and activate the merchant account.
2Configure credentialsSet Merchant ID, registered domain, API Key and Secret Key on the merchant server.
3Authenticate requestSign the merchant identity, domain, timestamp, nonce and exact JSON body.
4Submit withdrawalThe API accepts the request only after the merchant binding is verified.
1Check BalanceRead available and pending balance
2Create RequestSend recipient and amount details
3Reserve FundsAmount moves to pending balance
4Track StatusQuery the final processing state
02 API AUTHENTICATION

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.

HeaderDescriptionRequired
Content-Typeapplication/jsonYes
X-SBP-Merchant-IDThe Merchant ID shown in the Merchant Panel.Yes
X-SBP-Merchant-DomainThe exact registered website domain without protocol or path.Yes
X-SBP-API-KeyYour active merchant API Key.Yes
X-SBP-TimestampCurrent Unix timestamp in seconds, within five minutes.Yes
X-SBP-NonceUnique 16–100 character request value.Yes
X-SBP-SignatureHMAC-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'
);
03 CREATE WITHDRAWAL

Submit a withdrawal request

POSThttps://www.sendboxpay.cyou/api/v1/withdraw/create.php
FieldTypeRequiredDescription
order_idstringYesUnique merchant reference, 6–80 letters, numbers, hyphens or underscores.
amountdecimalYesGross amount with up to two decimal places.
payment_methodstringYesBKASH, NAGAD, ROCKET or BANK.
account_numberstringYesRecipient mobile wallet or bank account number, 6–50 characters.
account_namestringYesRecipient account holder name, 2–120 characters.
callback_urlstringNoHTTPS 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."
}
Balance reservation

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.

04 WITHDRAWAL STATUS

Query a withdrawal

POSThttps://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"
  }
}
StatusMeaningMerchant action
pendingRequest is being processed.Do not submit the same order again.
successWithdrawal completed.Mark the recipient payout complete.
failedWithdrawal failed.Review the final balance and recipient details.
cancelledWithdrawal was cancelled.Do not treat it as paid.
05 BALANCE API

Read merchant balance

POSThttps://www.sendboxpay.cyou/api/v1/balance.php

Send 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"
  }
}
06 CALLBACK / WEBHOOK

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.

HeaderDescription
X-SBP-EventEvent name, for example withdrawal.created.
X-SBP-TimestampUnix timestamp used in the signature.
X-SBP-SignatureHMAC-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');
});
07 ERROR HANDLING

HTTP status and common failures

HTTPMeaningCommon cause
201CreatedWithdrawal accepted and marked pending.
401UnauthorizedInvalid or disabled merchant credential, timestamp, nonce or signature.
403Domain not activeThe request domain does not match the domain registered for the Merchant ID.
404Not foundWithdrawal order does not exist.
409ConflictDuplicate order ID or nonce.
422Validation failedInvalid recipient data, amount, callback URL or insufficient balance.
{
  "success": false,
  "message": "Insufficient available balance."
}
08 SECURITY GUIDELINES

Withdrawal protection requirements

01

Use server-to-server requests only

Withdrawal endpoints must never be called directly from a browser or mobile client.

02

Match the registered domain

Use the exact Merchant Panel domain in every signed request. A different domain is rejected before processing.

03

Generate unique order IDs

Persist the merchant reference before sending the request and never reuse it for another withdrawal.

04

Validate recipient details

Confirm account ownership and payment method before submitting a request.

05

Protect credentials

Store the API Secret in an encrypted environment and rotate it immediately if exposure is suspected.

06

Verify status independently

Use the status endpoint before displaying a final result, and process each final state only once.

09 SAMPLE INTEGRATIONS

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
});
10
INTEGRATION PACKAGE

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.

Download Module