Look up customers
Read one of your customers by your own external id, and list that customer's payments — both scoped to your app (and, for payments, your credential's mode).
The customer lookup API lets you read back a customer you already created — by the
same customerReference you sent when accepting a payment —
and list that customer's payment history.
There are two endpoints, both under /api/v1/customers and both authenticated with
your app API key + secret:
GET /customers/{externalId}— the customer's contact identity only.GET /customers/{externalId}/payments— that customer's payments, newest first.
All amounts are integer minor units (paisa): 150000 means
1,500.00 BDT. Reads are scoped to your app — and, for the payments list, to your
credential's mode — so you can never see another app's (or
the other mode's) data. See App isolation.
Every response is wrapped in the standard envelope:
{ "data": <payload>, "meta": { "success": true, "message": null, "errorCode": null, "timestamp": "…" }, "pagination": null }.
The examples below show the data payload unless noted; see
Responses & errors.
The flow
Authentication & scope
Both endpoints require the app API-key headers; there is no request body. The
app_id is taken from the credential that authenticated the request, never from the
caller.
X-Api-Key: oi_test_8f3c1a9b2d4e
X-Api-Secret: sk_test_2b7e9f0a4c6d8e1fThe mode (TEST or LIVE) is derived from the API credential that authenticated the
request — never from the request body. There is one nuance worth understanding up
front, because the two endpoints treat mode differently:
| Endpoint | Scope | Mode behaviour |
|---|---|---|
GET /customers/{externalId} | app_id only | Mode-agnostic. A customer reference is shared across TEST/LIVE, so the identity lookup returns the same record regardless of which key you use. |
GET /customers/{externalId}/payments | app_id and mode | Mode-scoped. A oi_test_… key sees only test payments; a oi_live_… key only live ones. |
Path parameter for both endpoints:
| Field | Type | Required | Notes |
|---|---|---|---|
externalId | string (path) | yes | Your own identifier for the customer — the value you sent as customerReference on the payment. Not our internal id. |
Look up a customer
Read the contact identity
GET /customers/{externalId} returns the app's own customer reference — the external
id plus whatever optional contact fields you supplied at payment time. It carries no
internal id, credential, or cross-app data.
curl http://localhost:8080/api/v1/customers/cust_4821 \
-H "X-Api-Key: oi_test_8f3c1a9b2d4e" \
-H "X-Api-Secret: sk_test_2b7e9f0a4c6d8e1f"{
"data": {
"externalCustomerId": "cust_4821",
"name": "Rumana Karim",
"email": "[email protected]",
"phone": "+8801712345678"
},
"meta": { "success": true, "message": null, "errorCode": null, "timestamp": "2026-06-30T12:05:00Z" },
"pagination": null
}The lookup is keyed by (app_id, external_customer_id), so it resolves only your
own customers. An unknown id — or an id that belongs to another app — is a 404
that leaks nothing about whether the record exists elsewhere.
This endpoint is mode-agnostic: a customer_ref is shared across TEST and
LIVE, so the same id resolves to the same contact identity with either key.
Mode-scoped activity is served separately by the payments endpoint below.
Customer response (CustomerDto)
Contact identity only. Null fields are omitted from the JSON, so a customer who paid
without any name/email/phone returns just externalCustomerId.
| Field | Type | Notes |
|---|---|---|
externalCustomerId | string | Your own identifier for the customer (the customerReference you send). Always present. |
name | string | Last-seen display name, refreshed from each payment's non-blank customerName. Omitted when never supplied. |
email | string | Last-seen email, refreshed from each payment's non-blank customerEmail. Omitted when never supplied. |
phone | string | Last-seen phone, refreshed from each payment's non-blank customerPhone. Omitted when never supplied. |
See Customers for how a reference is find-or-created at payment time.
List a customer's payments
List, newest first
GET /customers/{externalId}/payments returns the app's payments for that customer,
ordered newest first. The result is scoped to your app_id and your
credential's mode.
curl http://localhost:8080/api/v1/customers/cust_4821/payments \
-H "X-Api-Key: oi_test_8f3c1a9b2d4e" \
-H "X-Api-Secret: sk_test_2b7e9f0a4c6d8e1f"{
"data": [
{
"id": 4821,
"reference": "PAY-7K2QF8M3ND",
"status": "SUCCEEDED",
"applicationId": 42,
"mode": "TEST",
"amountMinor": 150000,
"currency": "BDT",
"gateway": "SSLCOMMERZ",
"checkoutUrl": "http://localhost:3000/checkout/PAY-7K2QF8M3ND",
"metadata": { "orderId": "ord_99812", "cartRef": "cart_5567" },
"createdAt": "2026-06-30T12:00:00",
"updatedAt": "2026-06-30T12:04:18"
},
{
"id": 4733,
"reference": "PAY-3D9H2KQ7BW",
"status": "EXPIRED",
"applicationId": 42,
"mode": "TEST",
"amountMinor": 50000,
"currency": "BDT",
"gateway": "SSLCOMMERZ",
"checkoutUrl": "http://localhost:3000/checkout/PAY-3D9H2KQ7BW",
"createdAt": "2026-06-29T09:14:02",
"updatedAt": "2026-06-29T09:44:02"
}
],
"meta": { "success": true, "message": null, "errorCode": null, "timestamp": "2026-06-30T12:05:00Z" },
"pagination": null
}An unknown (or another app's) customer reference returns an empty array — not a
404. A miss has nothing to leak, so unlike the identity lookup there is no
"not found" here. Likewise, a test key listing a customer whose payments are all
live returns [].
Equivalent to the by-reference payment list
This endpoint is identical to the by-reference form on the payments API:
curl "http://localhost:8080/api/v1/payments?customerReference=cust_4821" \
-H "X-Api-Key: oi_test_8f3c1a9b2d4e" \
-H "X-Api-Secret: sk_test_2b7e9f0a4c6d8e1f"Both run the same app- and mode-scoped by-reference lookup and return the same
newest-first PaymentDto list — GET /customers/{externalId}/payments ≡
GET /payments?customerReference={externalId}. Use whichever fits your code path.
See Accept a payment.
Payment response (PaymentDto)
Each element is the same PaymentDto returned by create and the status lookup. Null
fields are omitted.
| Field | Type | Notes |
|---|---|---|
id | integer (int64) | Internal payment id; use it for GET /payments/{id}. |
reference | string | Opaque public reference (the bearer for hosted checkout/receipt). |
status | string | Payment lifecycle state, e.g. SUCCEEDED, PENDING, EXPIRED. |
applicationId | integer (int64) | Owning app id. |
mode | string | TEST or LIVE, derived from the credential — matches the key you listed with. |
amountMinor | integer (int64) | Amount in minor units (paisa). |
currency | string | BDT in v1. |
gateway | string | Gateway name, e.g. SSLCOMMERZ. |
gatewayTxnId | string | Gateway transaction id; set once known. |
checkoutUrl | string | The service-hosted checkout page for that payment. |
redirectUrl | string | The gateway-hosted page the checkout page forwards to; set while pending. |
invoiceId | integer (int64) | Linked invoice id, if any. |
metadata | JSON object | Your passthrough metadata, surfaced as a nested JSON object (not a string). |
createdAt | string (date-time) | When the intent was created. |
updatedAt | string (date-time) | Last status change. |
Error reference
All errors use the standard envelope with data: null and a machine-readable
meta.errorCode. See Responses & errors.
| HTTP | errorCode | When |
|---|---|---|
401 | UNAUTHORIZED | Missing, unknown, or mismatched X-Api-Key / X-Api-Secret (or a revoked/expired credential). The request is unauthenticated, so it never reaches the lookup. |
404 | RESOURCE_NOT_FOUND | GET /customers/{externalId} for an id your app has never seen — including an id that belongs to another app. The payments list never 404s; it returns [] instead. |
A lookup for an unknown (or cross-app) customer id:
{
"data": null,
"meta": {
"success": false,
"message": "Customer not found with id: cust_9999",
"errorCode": "RESOURCE_NOT_FOUND",
"timestamp": "2026-06-30T12:05:00Z"
},
"pagination": null
}Note the asymmetry: the identity lookup 404s for an unknown id, but the
payments list returns an empty array for the same unknown id. A missing
payment history is a normal state (a customer who hasn't paid in this mode), not an
error.
Related
Accept a payment
Create a payment with a customerReference — that's what creates the customer you look up here.
Customers
How a customer reference is find-or-created and what it stores.
Modes
Why the payments list is mode-scoped but the identity lookup is not.
App isolation
Every record carries app_id + mode; one app never sees another's.
Authentication
The X-Api-Key / X-Api-Secret pair and how it resolves your app and mode.
Entitlements
Read the active benefits a customer holds by the same reference.
Accept a payment
The end-to-end hosted-checkout flow — create an intent, redirect to checkout, react to the confirmed result, and read status and receipts.
Products, prices & benefits
Build your catalogue with the App API — sellable products, immutable prices, and the benefit entries that feed subscriptions, purchases and entitlements.