OI Payments Docs
Guides

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_2b7e9f0a4c6d8e1f

The 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:

EndpointScopeMode behaviour
GET /customers/{externalId}app_id onlyMode-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}/paymentsapp_id and modeMode-scoped. A oi_test_… key sees only test payments; a oi_live_… key only live ones.

Path parameter for both endpoints:

FieldTypeRequiredNotes
externalIdstring (path)yesYour 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.

FieldTypeNotes
externalCustomerIdstringYour own identifier for the customer (the customerReference you send). Always present.
namestringLast-seen display name, refreshed from each payment's non-blank customerName. Omitted when never supplied.
emailstringLast-seen email, refreshed from each payment's non-blank customerEmail. Omitted when never supplied.
phonestringLast-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}/paymentsGET /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.

FieldTypeNotes
idinteger (int64)Internal payment id; use it for GET /payments/{id}.
referencestringOpaque public reference (the bearer for hosted checkout/receipt).
statusstringPayment lifecycle state, e.g. SUCCEEDED, PENDING, EXPIRED.
applicationIdinteger (int64)Owning app id.
modestringTEST or LIVE, derived from the credential — matches the key you listed with.
amountMinorinteger (int64)Amount in minor units (paisa).
currencystringBDT in v1.
gatewaystringGateway name, e.g. SSLCOMMERZ.
gatewayTxnIdstringGateway transaction id; set once known.
checkoutUrlstringThe service-hosted checkout page for that payment.
redirectUrlstringThe gateway-hosted page the checkout page forwards to; set while pending.
invoiceIdinteger (int64)Linked invoice id, if any.
metadataJSON objectYour passthrough metadata, surfaced as a nested JSON object (not a string).
createdAtstring (date-time)When the intent was created.
updatedAtstring (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.

HTTPerrorCodeWhen
401UNAUTHORIZEDMissing, unknown, or mismatched X-Api-Key / X-Api-Secret (or a revoked/expired credential). The request is unauthenticated, so it never reaches the lookup.
404RESOURCE_NOT_FOUNDGET /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.

On this page