Integration docs
Base URL of this deployment's API. All request and response bodies are JSON; every
failure response carries { ok, error, code }. Authenticate every
app-plane call with the two headers below.
X-App-Id: <your app id>
X-App-Secret: <your app secret>
1 · Send an OTP
POST /v5/otp/send
{ "phone": "+8801XXXXXXXXX" }
201 { "ok": true, "sessionId": "...", "expiresAt": 1794000000 }
Phone must be strict E.164. Each send consumes one OTP credit —
402 insufficient_credits when the balance is zero. Rate limits answer
429 rate_limited (per-app per-number) and
429 resend_cooldown (per-number cooldown).
2 · Verify the code
POST /v5/otp/verify
{ "phone": "+8801XXXXXXXXX", "otp": "123456" }
200 { "ok": true, "verified": true }
Failure codes: 400 otp_expired (no active session / TTL passed),
423 otp_locked (too many attempts — retryAfterSec tells you
when), 400 invalid_otp_format / invalid_phone.
3 · Poll status
GET /v5/otp/status?phone=%2B8801XXXXXXXXX
200 { "ok": true, "status": "pending|verified|expired",
"expiresAt": 1794000000, "attemptsLeft": 5,
"lockedUntil": null, "verifiedAt": null }
404 not_found when the number never requested a session.
Error codes
| Status | code | Meaning |
|---|---|---|
| 400 | invalid_phone / invalid_otp_format | Number or code failed strict validation. |
| 400 | otp_expired | No active session for that number, or the TTL passed. |
| 402 | insufficient_credits | Credit balance is zero — buy a package in the dashboard. |
| 404 | not_found | No such session (status) or resource. |
| 409 | trx_id_exists | That bKash TrxID is already attached to another pending transaction. |
| 423 | otp_locked | Too many verify attempts; retry after retryAfterSec. |
| 429 | rate_limited / resend_cooldown | Per-app per-number rate limit, or per-number resend cooldown. |
Webhook signature
Deliveries to your app's webhook URL carry the header
X-DP-Signature: the hex-encoded HMAC-SHA256 of the raw request body,
keyed with your app's webhook secret. Verify it with a timing-safe comparison over
the raw bytes before parsing the JSON, and answer 2xx promptly — failed deliveries
are retried.
curl examples
Set the base URL once, then the three core calls:
# Your deployment's API base URL
API=https://dprelay-api-hug8.onrender.com
# 1) Send an OTP
curl -sS -X POST "$API/v5/otp/send" \
-H 'Content-Type: application/json' \
-H 'X-App-Id: <your app id>' \
-H 'X-App-Secret: <your app secret>' \
-d '{"phone":"+8801XXXXXXXXX"}'
# 2) Verify the code
curl -sS -X POST "$API/v5/otp/verify" \
-H 'Content-Type: application/json' \
-H 'X-App-Id: <your app id>' \
-H 'X-App-Secret: <your app secret>' \
-d '{"phone":"+8801XXXXXXXXX","otp":"123456"}'
# 3) Poll status
curl -sS -G "$API/v5/otp/status" \
--data-urlencode 'phone=+8801XXXXXXXXX' \
-H 'X-App-Id: <your app id>' \
-H 'X-App-Secret: <your app secret>'
Credits
Balance: GET /v5/billing/credits → both buckets plus their expiry.
Buying: GET /v5/billing/packages →
POST /v5/billing/credits/request { "packageCode" } returns the bKash
destination + amount → Send Money →
POST /v5/billing/credits/submit-trx { "transactionId", "trxId" } →
the operator approves and the balance updates. Track it all in
GET /v5/billing/transactions. TrxID reuse of a different pending
transaction answers 409 trx_id_exists. See the
payment guides.