Integration architecture
Integration becomes available after merchant configuration
https://www.sendboxpay.cyoubkash
Nagadnagad
Rocketrocket
Sign every API request
Merchant identity and API credentials are available from the Merchant Panel. Every request must include the Merchant ID, the exact registered website domain, API Key, Unix timestamp, unique nonce and an HMAC-SHA256 signature calculated from the complete signature payload.
| Header | Description | Required |
|---|---|---|
Content-Type | Must be 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. Maximum clock difference is five minutes. | Yes |
X-SBP-Nonce | A unique 16–100 character value using letters, numbers, underscore or hyphen. | Yes |
X-SBP-Signature | Lowercase hexadecimal HMAC-SHA256 signature. | Yes |
{merchant_id}\n{merchant_domain}\n{timestamp}\n{nonce}\n{raw_json_body}
signature = HMAC-SHA256(signature payload, API Secret)
PHP signature example
<?php
$payload = [
'order_id' => 'ORDER-10001',
'amount' => '500.00',
'customer_name' => 'Customer Name',
'customer_email' => 'customer@example.com',
'customer_phone' => '01712345678',
'callback_url' => 'https://merchant.example.com/webhooks/sendboxpay',
];
$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'
);
Create a checkout session
https://www.sendboxpay.cyou/api/v1/payment/create.phpRequest body
| Field | Type | Required | Description |
|---|---|---|---|
order_id | string | Yes | Unique merchant reference, 6–80 letters, numbers, hyphens or underscores. |
amount | decimal | Yes | Payment amount with up to two decimal places. |
customer_name | string | Yes | Customer name, 2–120 characters. |
customer_email | string | Yes | Valid customer email address. |
customer_phone | string | Yes | Bangladeshi mobile number in 01XXXXXXXXX format. |
method | string | No | bkash, nagad or rocket. When omitted, the customer selects an enabled method on checkout. |
callback_url | string | No | HTTPS endpoint for the payment completion webhook. Merchant profile URL is used when omitted. |
Request example
{
"order_id": "ORDER-10001",
"amount": "500.00",
"customer_name": "Customer Name",
"customer_email": "customer@example.com",
"customer_phone": "01712345678",
"callback_url": "https://merchant.example.com/webhooks/sendboxpay"
}
Success response · HTTP 201
{
"success": true,
"order_id": "ORDER-10001",
"invoice_id": "DMB-260716-A1B2C3",
"status": "pending",
"checkout_url": "https://www.sendboxpay.cyou/change-method.php?token=generated-session-token",
"message": "Checkout created successfully."
}
Query an order
https://www.sendboxpay.cyou/api/v1/payment/status.phpThe status endpoint uses the same authentication headers and signature process as the create endpoint.
{
"order_id": "ORDER-10001"
}
{
"success": true,
"payment": {
"invoice_id": "DMB-260716-A1B2C3",
"merchant_order_id": "ORDER-10001",
"amount": "500.00",
"status": "success",
"masked_number": "01712345678",
"transaction_id": "8N7K2P4M5Q",
"completed_at": "2026-07-16 14:30:25",
"method": "bkash"
}
}
| Status | Meaning | Recommended action |
|---|---|---|
| pending | Checkout has not been completed. | Keep the order unpaid and query again when needed. |
| success | Payment has been completed. | Fulfil once after webhook or verified status query. |
| failed | Payment failed. | Allow the customer to create a new order. |
| cancelled | Payment was cancelled. | Do not fulfil the order. |
Verify payment completion events
When a payment is completed, SendboxPay sends a JSON payment.completed event to the configured callback URL. Return any HTTP 2xx response after the signature and order data have been verified.
| Header | Description |
|---|---|
X-SBP-Event | Webhook event name, for example payment.completed. |
X-SBP-Timestamp | Unix timestamp used in webhook signature verification. |
X-SBP-Signature | HMAC-SHA256 of {timestamp}\n{raw_json_body} using your API Secret. |
{
"event": "payment.completed",
"sent_at": "2026-07-16T08:30:25+00:00",
"data": {
"order_id": "ORDER-10001",
"invoice_id": "DMB-260716-A1B2C3",
"status": "success",
"amount": "500.00",
"currency": "BDT",
"method": "bkash",
"transaction_id": "8N7K2P4M5Q",
"completed_at": "2026-07-16 14:30:25"
}
}
PHP webhook verification
<?php
$rawBody = file_get_contents('php://input') ?: '';
$timestamp = $_SERVER['HTTP_X_SBP_TIMESTAMP'] ?? '';
$receivedSignature = strtolower($_SERVER['HTTP_X_SBP_SIGNATURE'] ?? '');
$expectedSignature = hash_hmac(
'sha256',
$timestamp . "\n" . $rawBody,
'YOUR_API_SECRET'
);
if (!ctype_digit($timestamp)
|| abs(time() - (int) $timestamp) > 300
|| !hash_equals($expectedSignature, $receivedSignature)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
if (($event['event'] ?? '') === 'payment.completed'
&& ($event['data']['status'] ?? '') === 'success') {
// Update the matching order only once.
}
http_response_code(200);
echo 'OK';
HTTP status and error format
| HTTP | Meaning | Common cause |
|---|---|---|
201 | Created | Checkout session created successfully. |
401 | Unauthorized | Missing or disabled merchant credential, expired timestamp or invalid signature. |
403 | Domain not active | The request domain does not match the domain registered for the Merchant ID. |
404 | Not found | The requested merchant order does not exist. |
405 | Method not allowed | The endpoint received a request other than POST. |
409 | Conflict | Duplicate order ID or duplicate nonce. |
422 | Validation failed | Invalid amount, customer details, callback URL or unavailable method. |
{
"success": false,
"errors": [
"Invalid customer_email.",
"Unavailable payment method."
]
}
Required integration controls
Keep secrets server-side
Store API Secrets in environment variables or a protected secret manager. Never place them in HTML, JavaScript bundles, repositories or mobile applications.
Match the registered domain
Send the exact domain stored in the Merchant Panel and include it in the signature payload for every request.
Sign the exact raw body
Generate the JSON body once, sign that exact string and transmit the same string without rebuilding or reformatting it.
Use unique nonces
Create a cryptographically random nonce for every API call. Do not reuse a nonce within the acceptance window.
Verify amount and order ID
Before fulfilment, compare webhook data with the order stored on your server and process each successful order idempotently.
Use HTTPS callbacks
Use an HTTPS endpoint, verify webhook signatures and return a successful response only after durable processing.
Framework examples
<?php
function sendboxpayRequest(string $endpoint, array $payload): array
{
$merchantId = getenv('SENDBOXPAY_MERCHANT_ID');
$merchantDomain = getenv('SENDBOXPAY_MERCHANT_DOMAIN');
$apiKey = getenv('SENDBOXPAY_API_KEY');
$apiSecret = getenv('SENDBOXPAY_API_SECRET');
$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, $apiSecret);
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-SBP-Merchant-ID: ' . $merchantId,
'X-SBP-Merchant-Domain: ' . $merchantDomain,
'X-SBP-API-Key: ' . $apiKey,
'X-SBP-Timestamp: ' . $timestamp,
'X-SBP-Nonce: ' . $nonce,
'X-SBP-Signature: ' . $signature,
],
]);
$response = curl_exec($ch);
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false || $status < 200 || $status >= 300) {
throw new RuntimeException('Payment API request failed.');
}
return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
}
$result = sendboxpayRequest('https://www.sendboxpay.cyou/api/v1/payment/create.php', [
'order_id' => 'ORDER-' . time(),
'amount' => '500.00',
'customer_name' => 'Customer Name',
'customer_email' => 'customer@example.com',
'customer_phone' => '01712345678',
'callback_url' => 'https://merchant.example.com/webhooks/sendboxpay',
]);
header('Location: ' . $result['checkout_url']);
exit;
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use RuntimeException;
class SendboxPay
{
public function createPayment(array $payload): array
{
$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/payment/create.php');
if (!$response->successful()) {
throw new RuntimeException($response->body());
}
return $response->json();
}
}
import crypto from 'node:crypto';
export async function createPayment(payload) {
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/payment/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
});
const result = await response.json();
if (!response.ok) throw new Error(JSON.stringify(result));
return result;
}
import hashlib
import hmac
import json
import os
import secrets
import time
import requests
payload = {
"order_id": f"ORDER-{int(time.time())}",
"amount": "500.00",
"customer_name": "Customer Name",
"customer_email": "customer@example.com",
"customer_phone": "01712345678",
}
merchant_id = os.environ["SENDBOXPAY_MERCHANT_ID"]
merchant_domain = os.environ["SENDBOXPAY_MERCHANT_DOMAIN"]
body = json.dumps(payload, separators=(",", ":"))
timestamp = str(int(time.time()))
nonce = secrets.token_hex(16)
message = f"{merchant_id}\n{merchant_domain}\n{timestamp}\n{nonce}\n{body}".encode()
signature = hmac.new(
os.environ["SENDBOXPAY_API_SECRET"].encode(),
message,
hashlib.sha256,
).hexdigest()
response = requests.post(
"https://www.sendboxpay.cyou/api/v1/payment/create.php",
data=body,
headers={
"Content-Type": "application/json",
"X-SBP-Merchant-ID": merchant_id,
"X-SBP-Merchant-Domain": merchant_domain,
"X-SBP-API-Key": os.environ["SENDBOXPAY_API_KEY"],
"X-SBP-Timestamp": timestamp,
"X-SBP-Nonce": nonce,
"X-SBP-Signature": signature,
},
timeout=30,
)
response.raise_for_status()
print(response.json()["checkout_url"])
Download Deposit Module
Includes complete integration documentation, installation guide, example project structure, sample configuration, API client examples and webhook handlers for PHP, Laravel, Node.js and Python.
