Search pages in the SMS Pay documentation.
Your backend creates a payment intent for each order, stores the returned SMS Pay payment intent id, redirects the customer to hosted checkout, and fulfills only after payment.paid.
Do not create payment intents from browser JavaScript. API keys belong on your server only.
Create API keys from Dashboard -> API Keys.
| Key | Use |
|---|---|
| Sandbox key | Development, sandbox checkout, SMS simulator, webhook tests. |
| Live key | Real customer payments after launch readiness is complete. |
Store keys as environment variables:
SMS_PAY_BASE_URL=https://api.smspaybd.com/v1
SMS_PAY_API_KEY=sk_test_xxxxxxxxxxxxxxxxx
POST /v1/payments/intents
X-Api-Key: sk_test_xxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"amount": 500,
"currency": "BDT",
"merchantReference": "ORDER-10045",
"paymentMethod": "bkash_send_money",
"language": "bn",
"idempotencyKey": "payment_intent_ORDER-10045",
"ttlSeconds": 300,
"successUrl": "https://merchant.example.com/payments/success",
"failedUrl": "https://merchant.example.com/payments/failed",
"cancelUrl": "https://merchant.example.com/cart",
"expiredUrl": "https://merchant.example.com/payments/expired"
}
{
"id": "b5012f33-207e-4999-bf8d-5a1ebb10988e",
"environment": "SANDBOX",
"amount": "500",
"currency": "BDT",
"status": "PENDING",
"customerReference": "SP7A91BC2D0",
"merchantReference": "ORDER-10045",
"paymentMethod": "BKASH_SEND_MONEY",
"receiverMsisdn": "01700000001",
"checkoutUrl": "https://smspaybd.com/checkout/b5012f33-207e-4999-bf8d-5a1ebb10988e?language=bn",
"expiresAt": "2026-05-05T10:05:00.000Z"
}
Store id, checkoutUrl, customerReference, and merchantReference with your order.
| Field | Required | Merchant guidance |
|---|---|---|
amount | Yes | Amount to collect in BDT. Must match the wallet payment exactly. |
merchantReference | No | Your order or invoice number. Use this for long merchant references. |
customerReference | No | Short provider-facing reference, 16 characters or fewer. Let SMS Pay generate it unless you have a short safe code. |
idempotencyKey | Yes | Stable retry key for this order. Reusing it prevents duplicate intents on retry. |
paymentMethod | No | Selected method for checkout. Accepts canonical values like BKASH_SEND_MONEY or lowercase aliases like bkash_send_money. Omit it when hosted checkout should let the customer choose. |
language | No | Hosted checkout language: en or bn. Defaults to en. This is not stored on the payment intent. |
| Redirect URLs | No | Customer browser destinations only. Do not use them as fulfillment proof. |
Supported canonical payment methods are BKASH_PAYMENT, BKASH_SEND_MONEY, BKASH_CASHOUT, NAGAD_PAYMENT, NAGAD_SEND_MONEY, and NAGAD_CASHOUT. A selected method must be enabled for your merchant and have an active receiver account.
If paymentMethod is omitted, hosted checkout shows available payment options when more than one method is enabled. If only one method is available, checkout selects it automatically.
Use merchantReference for your order number, for example ORDER-1779722287145-9441.
Use customerReference only for the payment reference shown to the customer and wallet provider. It must be short and provider-safe. If omitted, SMS Pay generates a short unique reference.
Use idempotencyKey to safely retry creating the same payment intent from your backend. It is not an order number and it is not shown to the customer.
export async function createSmsPayCheckout(order: {
id: string;
amount: number;
}) {
const response = await fetch(`${process.env.SMS_PAY_BASE_URL}/payments/intents`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": process.env.SMS_PAY_API_KEY!,
},
body: JSON.stringify({
amount: order.amount,
currency: "BDT",
merchantReference: order.id,
paymentMethod: "bkash_send_money",
language: "bn",
idempotencyKey: `payment_intent_${order.id}`,
ttlSeconds: 300,
successUrl: "https://merchant.example.com/payments/success",
failedUrl: "https://merchant.example.com/payments/failed",
cancelUrl: "https://merchant.example.com/cart",
expiredUrl: "https://merchant.example.com/payments/expired"
}),
});
if (!response.ok) {
throw new Error(await response.text());
}
return response.json() as Promise<{
id: string;
checkoutUrl: string;
customerReference: string;
merchantReference: string | null;
}>;
}
After redirecting the customer to checkoutUrl, wait for a signed payment.paid webhook or retrieve the payment intent from your backend and confirm status === "PAID".
Never fulfill from successUrl alone.