Developer portal · sandbox
PayBangla API
These examples call the routes this server implements. Test keys use the pb_test_ prefix. livePayment is always false. A customer transaction ID does not approve a payment.
API overview
Create a payment, send the customer to the hosted checkout, then read the status. The status becomes PENDING_VERIFICATION after submission and APPROVED only after an operator records an independent check.
Authentication
Send Authorization: Bearer pb_test_…. Create calls also require Idempotency-Key of 8 to 80 characters. The same key and body replay the first response. A different body returns 409. Revoked keys return 401. Live keys are not issued.
Create payment
POST /api/v1/payments POST /api/v1/payment-links POST /api/v1/payment-requests
Body fields: orderId, amount, optional currency (only BDT), description, customerName, customerPhone, customerReference, merchantReference, returnUrl, and expiresInHours from 1 to 168. The response includes paymentId, checkoutUrl, status, and expiresAt.
Node.js
const response = await fetch(process.env.PAYBANGLA_URL + "/api/v1/payments", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.PAYBANGLA_TEST_KEY,
"Content-Type": "application/json",
"Idempotency-Key": "order-1001-attempt-1",
},
body: JSON.stringify({
orderId: "ORDER-1001",
amount: "1250.00",
currency: "BDT",
description: "Sandbox order",
}),
});
const payment = await response.json();
// payment.status is AWAITING_PAYMENT
// payment.checkoutUrl is the hosted checkout
// payment.livePayment is falsePHP
$ch = curl_init($base . "/api/v1/payments");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("PAYBANGLA_TEST_KEY"),
"Content-Type: application/json",
"Idempotency-Key: order-1001-attempt-1",
],
CURLOPT_POSTFIELDS => json_encode([
"orderId" => "ORDER-1001",
"amount" => "1250.00",
"currency" => "BDT",
]),
CURLOPT_RETURNTRANSFER => true,
]);
$payment = json_decode(curl_exec($ch), true);Python
import os, requests
payment = requests.post(
os.environ["PAYBANGLA_URL"] + "/api/v1/payments",
headers={
"Authorization": "Bearer " + os.environ["PAYBANGLA_TEST_KEY"],
"Idempotency-Key": "order-1001-attempt-1",
},
json={"orderId": "ORDER-1001", "amount": "1250.00", "currency": "BDT"},
timeout=20,
).json()Hosted checkout
Open checkoutUrl. It accepts the payment id or the original checkout token. Only enabled methods with an active receiving account are shown. Submission stores a tracking reference and sets PENDING_VERIFICATION.
Payment status
GET /api/v1/payments/:id GET /api/v1/payments GET /api/v1/payment-links/:id
Disable an unused link with POST /api/v1/payment-links/:id. After a customer submits, the amount cannot be changed and the link cannot be disabled.
Webhooks
The stored event type and the merchant-facing event name are both real. PAYMENT_SUCCESS is the same delivery as payment.approved. It is queued only after manual approval. The receiver must return a 2xx response. A failed response is retried with exponential backoff. Signatures use PayBangla-Signature: t=<unix>,v1=<hmac sha256 of timestamp.body> and expire after five minutes.
payment.created PAYMENT_CREATED payment.pending PAYMENT_PENDING payment.under_review PAYMENT_UNDER_REVIEW payment.approved PAYMENT_SUCCESS payment.rejected PAYMENT_REJECTED payment.expired PAYMENT_EXPIRED webhook.test connectivity only
Events are written in the same database transaction as the payment change, then delivered by the webhook job. A delivery is marked delivered only after a 2xx response. Rotate the webhook secret from the merchant desk. The secret is not included in API responses after that response. Endpoint checks reject private addresses.
Error codes
401 missing or revoked key. 403 merchant not approved or live collection disabled. 409 duplicate order, idempotency conflict, or a second decision. 400 validation, including a non-BDT currency. 429 rate limit.
Sandbox testing
Register a merchant, open the verification link from signup, and wait for an operator to approve sandbox access. Generate a key from the merchant desk. It is shown once. The seeded bKash receiving number is a labeled sandbox fixture, not a live wallet.