Search pages in the SMS Pay documentation.
Set up a webhook endpoint so SMS Pay can notify your backend when payment status changes. Merchant admins configure endpoints in the dashboard; developers implement the receiving route.
https://merchant.example.com/webhooks/sms-pay.Production endpoints must use HTTPS.
Subscribe to:
| Event | Recommended handling |
|---|---|
payment.paid | Mark the order paid and fulfill. |
payment.review_required | Alert operations; do not fulfill. |
payment.expired | Mark the payment attempt expired. |
payment.rejected | Mark the attempt rejected after review. |
For most merchants, one endpoint subscribed to all four events is simplest.
SMS_PAY_WEBHOOK_SECRET=whsec_minimum_16_characters
Keep the secret private. Rotate it if it is exposed.
Verify X-Webhook-Signature against the raw request body before parsing JSON.
import crypto from "node:crypto";
import express from "express";
const app = express();
const secret = process.env.SMS_PAY_WEBHOOK_SECRET!;
function safeEqual(left: string, right: string) {
const a = Buffer.from(left);
const b = Buffer.from(right);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post(
"/webhooks/sms-pay",
express.raw({ type: "application/json" }),
async (req, res) => {
const rawBody = req.body as Buffer;
const signature = req.header("x-webhook-signature") ?? "";
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
if (!safeEqual(signature, expected)) {
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(rawBody.toString("utf8"));
await saveWebhookOnce(webhookId, payload);
if (eventType === "payment.paid") {
await markOrderPaid({
smsPayPaymentIntentId: payload.data.payment_intent_id,
merchantReference: payload.data.merchant_reference,
});
}
return res.status(200).send("ok");
},
);
{
"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"
}
}
Use merchant_reference to find your order when available. Use payment_intent_id as the stable SMS Pay fallback.
Verify checks whether your endpoint can receive SMS Pay verification requests. Verification is a setup health check; it does not prove your fulfillment logic is complete.
Test sends a selected event to your endpoint and records the delivery result. Use it to confirm:
2xxDashboard -> Webhooks shows delivery status, HTTP status, attempts, payload, response body, and retry state. Use this page when an order did not update in your system.