A Practical Guide to Integrating bKash into a Next.js App
The parts of bKash Tokenized Checkout the docs skim over — sandbox flakiness, idempotency, timeout handling, and getting your callback URL past the compliance review.

The bKash sandbox will pass on a Wednesday and fail on a Friday for reasons the support ticket will not explain. Anyone integrating tokenized checkout for the first time budgets one week and burns three. This is the recipe we use every time now, and the specific traps to plan around.
**The flow, honestly**
Grant Token → Create Payment → Redirect the customer to bKash's URL → bKash redirects back to your callback → Execute Payment → verify → mark the order paid. Five API calls, four of which are yours to make correctly, one of which is bKash's to hand you a fresh `paymentID` on the callback. Every step is where a real integration fails once, and the failure is always the same: you assumed idempotency and bKash gave you no such thing.
**Grant token — cache it, but not for long**
The grant-token endpoint returns an `id_token` valid for one hour. Cache it in Redis or upstash keyed by the sandbox/live scope, with a TTL of 55 minutes. Re-issuing per request will get you rate-limited within a day of your first campaign. Refreshing eagerly ten seconds before expiry — not lazily on failure — is what keeps the checkout flow smooth during a sale spike.
**Create payment — bind your invoice, not the customer**
The `merchantInvoiceNumber` is the field bKash echoes back in reconciliation. Use your internal order id, not the customer's phone number, not a random UUID. A single order can have multiple `paymentID`s if the customer starts, drops off, and retries; correlate on YOUR id, not theirs.
**The callback is user-controlled, treat it as adversarial**
The redirect back to your site includes `paymentID` and `status` in the query string. Never trust `status=success`; always call Execute Payment server-side and use its response. A determined attacker will hand-craft a callback URL that reads success; the Execute call is the only source of truth.
**Timeout handling — 30 seconds is not enough**
bKash's Execute endpoint occasionally takes 45–90 seconds under load. Set your HTTP client timeout to 120 seconds for that specific call. When it does time out, do NOT retry immediately — bKash may have processed the payment; retry will double-charge. Instead, poll the Query Payment endpoint every ten seconds for up to five minutes; that endpoint IS safe to retry.
**Getting past compliance review**
Bangladesh Bank's Payment Systems Department reviews every new integration before merchant IDs move from sandbox to live. Three things speed this up: a real settlement bank account under your business name, a callback URL served over HTTPS with a certificate from a public CA (Let's Encrypt is fine; self-signed will fail), and a merchant name that matches your trade licence exactly, including punctuation.
**The Next.js specifics**
Put the Grant Token call in a server action, not an API route — it needs to run before the redirect and shouldn't be reachable by the browser. Store `paymentID` in a signed cookie between redirect and callback so a page refresh doesn't lose the transaction. And rate-limit the callback endpoint by IP; bKash's own webhook has never in three years of production issued more than ten calls a second, so anything above that is either a stress test or an attack.
We have shipped bKash checkout in eleven client apps. The playbook above is what we start every twelfth project from.
