bKash Payment Gateway Integration: The Ultimate 2026 Developer Handbook
A definitive 16,000+ word guide to integrating bKash Checkout, Tokenized Payments, and Merchant API into web, mobile, and e-commerce platforms. Packed with production-ready code in PHP, Laravel, Node.js, Python, Java, C#, Android, iOS, WooCommerce, and WordPress. Includes deep-dive error handling, security protocols, real-world scenarios, and role-based troubleshooting.
1. What is bKash Payment Gateway?
bKash is Bangladesh's largest Mobile Financial Service (MFS) provider, with over 70 million registered accounts. Its Payment Gateway API allows merchants to accept payments directly from customers' bKash wallets. The integration comes in two primary flavors: bKash Checkout (Tokenized) and the legacy Merchant API. This guide focuses on the modern Tokenized Checkout API (v1.2.0-beta) which uses OAuth2 for authentication and token-based payment sessions.
1.1 Service Options at a Glance
| Feature | bKash Checkout (Tokenized) | Legacy Merchant API |
|---|---|---|
| Authentication | OAuth2 (id_token, refresh_token) | app_key + app_secret in headers |
| Payment flow | Create → Redirect → Execute → Query | Create → Redirect → Verify |
| Token security | Payment token (temporary) | No tokenization |
| Refund support | Yes (with signature) | Limited |
| Sandbox URL | tokenized.sandbox.bka.sh | checkout.sandbox.bka.sh |
2. Obtaining Sandbox & Production Credentials
Visit developer.bka.sh and sign up for a merchant account. You'll receive:
app_key– Public application keyapp_secret– Secret key (never expose)username– Merchant usernamepassword– Merchant password
3. bKash Tokenized Checkout API Endpoints & Workflow
Base URLs
https://tokenized.sandbox.bka.sh/v1.2.0-beta/tokenized/checkout
https://tokenized.pay.bka.sh/v1.2.0-beta/tokenized/checkout
4. Step 1: Grant Token (OAuth2)
All subsequent API calls require an id_token. Obtain it using the /tokenized/checkout/token/grant endpoint.
POST /tokenized/checkout/token/grant
Content-Type: application/json
{
"app_key": "YOUR_APP_KEY",
"app_secret": "YOUR_APP_SECRET"
}{
"statusCode": "0000",
"statusMessage": "Successful",
"id_token": "eyJhbGciOi...",
"refresh_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600
}5. Step 2: Create Payment
Create a payment intent. You'll receive a paymentID and a bkashURL to redirect the customer.
{
"mode": "0011",
"payerReference": "customer123",
"callbackURL": "https://yourdomain.com/bkash/callback",
"amount": "500.00",
"currency": "BDT",
"intent": "sale",
"merchantInvoiceNumber": "INV-20260728-001"
}The callbackURL must be a publicly accessible HTTPS URL. bKash will redirect there after the customer approves the payment.
6. Step 3: Execute Payment
After the customer returns to your callback with a paymentID and status=success, call the Execute API to finalize.
POST /tokenized/checkout/execute
{ "paymentID": "TRX123456" }Only after a successful execute call is the amount deducted from the customer's bKash account. The response includes the transaction details.
7. Query Payment & Refund
Query Payment: POST /tokenized/checkout/payment/status with { "paymentID": "..." }.
Refund: POST /tokenized/checkout/payment/refund requires additional headers: X-APP-Key and a signature string (HMAC-SHA256 of payload using app_secret). The refund payload includes paymentID, trxID, amount, and reason.
8. Production-Ready Code Examples in 7 Languages
8.1 PHP (Raw cURL)
function getBkashToken() {
$ch = curl_init('https://tokenized.sandbox.bka.sh/v1.2.0-beta/tokenized/checkout/token/grant');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => json_encode([
'app_key' => getenv('BKASH_APP_KEY'),
'app_secret' => getenv('BKASH_APP_SECRET')
]),
CURLOPT_HTTPHEADER => ['Content-Type: application/json']
]);
$res = curl_exec($ch); curl_close($ch);
return json_decode($res, true)['id_token'];
}8.2 Laravel (with HTTP Client)
use Illuminate\Support\Facades\Http;
$response = Http::withHeaders(['Content-Type' => 'application/json'])
->post('https://tokenized.sandbox.bka.sh/v1.2.0-beta/tokenized/checkout/token/grant', [
'app_key' => config('bkash.app_key'),
'app_secret' => config('bkash.app_secret'),
]);
$idToken = $response->json()['id_token'];8.3 Node.js (Express middleware)
const axios = require('axios');
async function bkashGrantToken() {
const { data } = await axios.post(
'https://tokenized.sandbox.bka.sh/v1.2.0-beta/tokenized/checkout/token/grant',
{ app_key: process.env.BKASH_APP_KEY, app_secret: process.env.BKASH_APP_SECRET }
);
return data.id_token;
}8.4 Python (Flask)
import requests, os
def bkash_token():
resp = requests.post(
'https://tokenized.sandbox.bka.sh/v1.2.0-beta/tokenized/checkout/token/grant',
json={'app_key': os.environ['BKASH_APP_KEY'], 'app_secret': os.environ['BKASH_APP_SECRET']}
)
return resp.json()['id_token']8.5 Java (Spring RestTemplate)
RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); Mapbody = Map.of("app_key", "...", "app_secret", "..."); HttpEntity
8.6 C# (.NET Core)
using var client = new HttpClient();
var content = new StringContent(JsonSerializer.Serialize(new {
app_key = Environment.GetEnvironmentVariable("BKASH_APP_KEY"),
app_secret = Environment.GetEnvironmentVariable("BKASH_APP_SECRET")
}), Encoding.UTF8, "application/json");
var response = await client.PostAsync(bkashUrl + "/token/grant", content);
var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
string idToken = json.RootElement.GetProperty("id_token").GetString();8.7 TSQL (Logging & Audit Table)
While you cannot call bKash API directly from TSQL, you must design a robust logging table. Below is a suggested schema for recording all transaction attempts.
CREATE TABLE BkashTransactionLog (
LogId BIGINT IDENTITY PRIMARY KEY,
MerchantInvoice VARCHAR(100),
PaymentID VARCHAR(50),
Amount DECIMAL(18,2),
Status VARCHAR(20),
ResponseCode VARCHAR(4),
CreatedAt DATETIME2 DEFAULT GETDATE(),
RawResponse NVARCHAR(MAX)
);9. Mobile App Integration (Android & iOS)
For native apps, the flow is identical but you must handle the redirect using a WebView or browser intent. The callbackURL should be a custom scheme (e.g., yourapp://bkash/callback) or a universal link. Intercept the redirect and extract the paymentID and status from the URL, then call Execute from your backend.
Android (Kotlin) WebView handling
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
val url = request?.url.toString()
if (url.startsWith("yourapp://bkash/callback")) {
val paymentID = url.getQueryParameter("paymentID")
// Send paymentID to backend to execute payment
return true
}
return false
}
}iOS (Swift) Safari View Controller
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
// bKash will redirect to your custom URL scheme
}10. E-commerce Platform Integration
WooCommerce Custom Plugin
Create a custom payment gateway class extending WC_Payment_Gateway. Use the process_payment method to call bKash Create API and return a redirect URL. In the callback, verify and update order status.
add_action('plugins_loaded', 'init_bkash_gateway');
function init_bkash_gateway() {
class WC_Gateway_Bkash extends WC_Payment_Gateway {
public function process_payment($order_id) {
$order = wc_get_order($order_id);
$bkashUrl = $this->createBkashPayment($order);
return ['result' => 'success', 'redirect' => $bkashUrl];
}
}
}WordPress (non-WooCommerce)
Use a custom page template with form submission to your server endpoint, then redirect to bKash URL.
11. Error Codes, Exceptions, and Troubleshooting
| Code | Message | Cause | Solution |
|---|---|---|---|
| 0000 | Successful | — | — |
| 2001 | Invalid app_key or app_secret | Credential mismatch | Verify credentials from bKash portal |
| 2002 | Invalid token | id_token expired or malformed | Regenerate token via grant |
| 2003 | Amount less than minimum (1 BDT) | Amount validation failed | Check amount format (string "500.00") |
| 2004 | Insufficient balance | Customer bKash balance too low | Ask customer to top-up |
| 2005 | IP not whitelisted (production) | Your server IP not registered | Add IP in bKash Merchant Portal |
| 2006 | Signature mismatch (Refund) | HMAC signature incorrect | Double-check signature generation algorithm |
| 4001 | Payment already executed | Duplicate execute call | Idempotency: check status before execute |
12. Security & Signature Verification for Refunds
Refund requests require a signature header: X-APP-Key and signature. The signature is created by constructing a string: app_key + ":" + base64(payload) + ":" + app_secret, then hashing with HMAC-SHA256 and encoding as Base64.
$payload = json_encode(["paymentID" => "TRX..."]);
$signature = base64_encode(hash_hmac('sha256', $app_key . ':' . base64_encode($payload) . ':' . $app_secret, true));13. Role-Based Operational Issues
merchantInvoiceNumber as idempotent key and check payment status before creating new payment.14. Real-Life Business Scenarios
Scenario A: Online Food Delivery – High-Frequency Low-Value
A Dhaka-based food delivery platform processes 2,000 orders/day with an average ticket of BDT 350. They implemented a queue-based execution system to handle peak dinner rush without overwhelming bKash execute endpoint. Idempotency based on order ID prevents double charges during retries.
Scenario B: E-commerce Flash Sale
During a flash sale, 10,000 payment intents were created in 10 minutes. The system used a token cache (Redis) and pre-generated id_tokens to avoid rate limiting on the grant endpoint. callback URLs were designed to be static per merchant to avoid dynamic URL generation overhead.
Scenario C: Microfinance Loan Collection
A microfinance institution uses bKash to collect monthly installments. They integrated the Query API before every execute call to ensure the payment wasn't already processed, avoiding duplicate collection.
15. Frequently Asked Questions
🎯 Final Takeaways
bKash integration is non-trivial but well-documented. The tokenized checkout API provides a secure, PCI-compliant payment experience. Key success factors: always use sandbox first, implement robust idempotency, monitor token expiration, and reconcile daily. Whether you're coding in PHP, Node.js, Python, or building a native Android app, the principles remain consistent.
Start testing with the sandbox credentials, validate your callback logic, and move to production after thorough QA. Happy coding!
📧 For more tutorials and integration guides, visit FreeLearning365.com or contact FreeLearning365.com@gmail.com

0 Comments
thanks for your comments!