Search pages in the SMS Pay documentation.
Copy this prompt into an AI assistant when you want it to generate SMS Pay integration code for your application.
You are helping me integrate SMS Pay into my application.
SMS Pay is a hosted payment gateway for Bangladesh mobile wallet payments. My backend creates a payment intent, redirects the customer to a hosted checkout URL, SMS Pay confirms payment from provider SMS evidence, and my backend receives signed webhooks when payment status changes.
Base URL:
https://api.smspaybd.com/v1
Authentication:
All merchant backend API calls use:
X-Api-Key: <sandbox_or_live_key>
Never expose API keys in browser code.
Core flow:
1. My server creates a payment intent with POST /v1/payments/intents.
2. My server stores the returned payment intent ID with my order.
3. My server redirects the customer to checkoutUrl.
4. SMS Pay hosted checkout handles payment method selection.
5. SMS Pay receives provider SMS through its Android app or sandbox simulator.
6. SMS Pay matches SMS evidence to the payment intent.
7. My server receives a signed webhook.
8. My server fulfills only after payment.paid.
Checkout behavior:
- If one payment method is enabled, checkout opens the payment screen directly.
- If multiple methods are enabled, checkout shows them unselected and Continue is disabled until the customer selects one.
- paymentMethod is optional when creating an intent for hosted checkout.
Create payment intent:
POST /v1/payments/intents
Content-Type: application/json
X-Api-Key: sk_test_xxxxxxxxxxxxxxxxx
Request:
{
"amount": 500,
"currency": "BDT",
"merchantReference": "ORDER-10045",
"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"
}
customerReference is optional. If omitted, SMS Pay generates a short provider-facing payment reference. If supplied, it must be unique per merchant environment and 16 characters or fewer after normalization. Do not put long ecommerce order numbers such as ORD-1779771263742-7236 in customerReference. Use merchantReference for long order or invoice IDs.
Response:
{
"id": "b5012f33-207e-4999-bf8d-5a1ebb10988e",
"environment": "SANDBOX",
"amount": "500",
"currency": "BDT",
"status": "PENDING",
"customerReference": "SP7A91BC2D0",
"merchantReference": "ORDER-10045",
"checkoutUrl": "https://smspaybd.com/checkout/b5012f33-207e-4999-bf8d-5a1ebb10988e",
"expiresAt": "2026-05-05T10:05:00.000Z"
}
Retrieve payment intent:
GET /v1/payments/intents/:id
X-Api-Key: sk_test_xxxxxxxxxxxxxxxxx
Important statuses:
- PENDING: waiting for payment evidence
- PAID: confirmed, fulfill order
- REVIEW_REQUIRED: needs manual review, do not fulfill
- EXPIRED: expired, do not fulfill
- FAILED or REJECTED: do not fulfill
Webhook events:
- payment.paid
- payment.review_required
- payment.expired
- payment.rejected
Webhook payload:
{
"event": "payment.paid",
"environment": "SANDBOX",
"timestamp": "2026-05-05T10:01:00.000Z",
"data": {
"payment_intent_id": "b5012f33-207e-4999-bf8d-5a1ebb10988e",
"amount": "500",
"currency": "BDT",
"customer_reference": "SP7A91BC2D0",
"merchant_reference": "ORDER-10045"
}
}
Webhook headers:
- X-Webhook-Id
- X-Webhook-Event
- X-Webhook-Signature
- X-Webhook-Timestamp
- X-Webhook-Idempotency-Key
Webhook signature:
The signature is HMAC-SHA256 of the raw request body using my webhook secret.
It is sent as:
X-Webhook-Signature: sha256=<hex>
Node/Express verification example:
const crypto = require("crypto");
const express = require("express");
const app = express();
function verifyWebhook(rawBody, signatureHeader, secret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(signatureHeader || "");
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post("/webhooks/sms-pay", express.raw({ type: "application/json" }), async (req, res) => {
const signature = req.header("x-webhook-signature");
if (!verifyWebhook(req.body, signature, process.env.SMS_PAY_WEBHOOK_SECRET)) {
return res.status(401).send("invalid signature");
}
const webhookId = req.header("x-webhook-id");
const eventType = req.header("x-webhook-event");
const payload = JSON.parse(req.body.toString("utf8"));
// Store webhookId or X-Webhook-Idempotency-Key so retries do not fulfill twice.
// If already processed, return 200.
if (eventType === "payment.paid") {
// Mark order paid using payload.data.merchant_reference or payment_intent_id.
}
return res.status(200).send("ok");
});
Fulfillment rules:
- Never fulfill only from a browser redirect.
- Fulfill after payment.paid webhook, or after my backend retrieves the intent and confirms status is PAID.
- Handle webhooks idempotently because SMS Pay may retry.
- Treat payment.review_required as not paid.
Sandbox testing:
- Use Dashboard -> Sandbox -> SMS Simulator to submit simulated SMS.
- Use Scenario Lab to test success, mismatch, duplicate, expiry, malformed SMS, unsupported sender, and webhook cases.
- Use Webhook Testing to send payment.paid, payment.review_required, and payment.expired test events.
- Use Activity Logs to trace SMS generation, submission, parsing, matching, and webhook delivery.
Android app:
- SMS Pay already provides the Android app.
- Merchants install the APK from /downloads/sms-pay_v1.apk or scan the QR code in the docs.
- Do not generate Android app or SMS forwarding code; integrate only my merchant backend unless I explicitly ask otherwise.
Generate code for my framework that:
- creates payment intents server-side
- sends my ecommerce order number as merchantReference, not customerReference
- omits customerReference unless it is a short provider-safe value of 16 characters or fewer
- redirects to checkoutUrl
- verifies webhook signature from the raw body
- handles payment.paid idempotently
- stores payment status safely in my database
- never exposes API keys in the browser
Tips: