API REFERENCE · v1

Verify payments API

A single REST endpoint that confirms Ethiopian transactions against the institution's own record. Send a transaction reference or a full receipt URL - with a bank code where it can't be auto-detected - to receive a normalized verification object that's identical across CBE, Telebirr, M-Pesa, and every other supported provider.

Verified transactions are cached: repeat lookups are served instantly from the database, and the confirmation object tracks how many times a transaction has been independently verified.

BASE https://pay.lubaak.com/api/v1

Authentication

/v1/verify works without any credentials - anonymous calls are permitted for evaluation, subject to a stricter rate limit (see Rate limits). Every other endpoint (saved accounts, token management) requires a bearer token.

Create an account at POST /auth/register, or exchange existing credentials at POST /auth/login - both return a personal access token in the response body. Send it on every subsequent request:

Authorization: Bearer LNjMOFktw2rGV5F8u7uAxxxxxxxxxxxxxxxxxxxx

Passing a token to /v1/verify is optional but removes the anonymous rate limit entirely, and attributes the verification to your account in /auth/tokens's usage history.


GET POST /v1/verify

Verifies a single transaction. Accepts either a query string (GET) or a JSON body (POST) - same parameters either way. Supply either url or reference; the rest are optional enrichments.

Parameters

Parameter Type Required Description
url string conditional Full bank receipt URL. Bank is auto-detected from the hostname unless bank is also given. Required unless reference is supplied.
reference string conditional Transaction / FT number. Required unless url is supplied. Auto-detectable only for cbe (FT-prefixed), telebirr, and zemen - every other bank needs the bank parameter alongside it.
bank string conditional Institution code - cbe, cbemobile, cbebirr, boa, mpesa, awash, dashen, telebirr, zemen, wegagen, nib, siinqee, abay, kaafiebirr, zamzam. See Supported banks.
phone string conditional Required when bank=cbebirr and reference is used (ignored for a url lookup).
suffix string optional Appended to reference to complete the full transaction ID. Only used for cbe and boa; ignored otherwise.
settlement_account string optional Account number or phone the payment should have landed in. Adds a settlement_match object to the response.
expected_amount number optional If set, the result flags any mismatch against the settled amount in amount_match.

Try it

Response - · -
// hit Send to try a live request

The verification object

Every successful call returns the same top-level shape, regardless of the underlying institution. The data object holds bank-specific fields.

FieldTypeDescription
valid boolean true only when the transaction exists and has settled.
bank string Echo of the institution code that was checked.
status enum verified · invalid · pending.
cached boolean true if served from database cache rather than fetched live.
data object Bank-specific transaction fields. See Data fields below.
confirmation object Present on cached responses. Tracks how many times the transaction has been independently verified.
settlement_match object Present when settlement_account was provided. Indicates whether the receiver matches.
amount_match object Present when expected_amount was provided. Compares expected vs. settled amounts.

Response format

Verified transaction

200 OK · status: verified
{
  "valid":  true,
  "status": "verified",
  "bank":   "boa",
  "cached": false,
  "data": {
    "sender_name":      "MUBAREK SEID JUHAR",
    "sender_account":   "1******77",
    "receiver_name":    "ELIAS WELDU REZAKUM",
    "receiver_account": "1**********08",
    "receiver_bank":    "Commercial Bank of Ethiopia",
    "sent_amount":      "10.00 ETB",
    "service_charge":   "0.06 ETB",
    "tax_vat":          "0.01 ETB",
    "total_paid":       "10.07 ETB",
    "transfer_date":    "14/06/26 12:46",
    "transaction_id":   "FT26166XLKCN",
    "transaction_type": "Other Bank Transfer",
    "reason":           "P2P Transaction"
  }
}

Pending (bank temporarily unreachable)

When the bank API times out, a soft pending state is returned instead of a hard 502 error. No credits are deducted; clients should retry.

200 OK · status: pending
{
  "valid":  false,
  "status": "pending",
  "bank":   "cbe",
  "cached": false,
  "data": {
    "error": "Bank service is temporarily unreachable. Please try again in a moment."
  }
}

Invalid transaction

Not-found or malformed transactions are still a 200 OK - the bank was reachable, it just doesn't recognize the reference. status is invalid and valid is false.

200 OK · status: invalid
{
  "valid":  false,
  "status": "invalid",
  "bank":   "boa",
  "cached": false,
  "data": {
    "transaction_id": "",
    "error":          "Invalid reference number"
  }
}

Cached response

When a transaction was previously verified and stored, the data object is identical but cached is true and a confirmation object is added.

200 OK · cached
{
  "valid":  true,
  "status": "verified",
  "bank":   "boa",
  "cached": true,
  "data":   { ... },
  "confirmation": {
    "confirmed_count":    4,
    "first_confirmed_at": "2026-06-01T09:12:00+03:00",
    "last_confirmed_at":  "2026-06-15T14:33:00+03:00"
  }
}

Settlement match

When settlement_account is provided, the response includes a settlement_match object. Match types: exact · suffix · masked_pattern · phone_normalised · unmatched.

200 OK · with settlement_account
{
  "valid": true,
  "data":  { ... },
  "settlement_match": {
    "matched":          true,
    "match_type":       "phone_normalised",
    "match_confidence": "high"
  }
}

X-Verify-Cache header

Every response carries an X-Verify-Cache header indicating whether the result came from the live upstream or the local database cache.

Response headers
X-Verify-Cache: HIT   // served from database cache
X-Verify-Cache: MISS  // fetched live from the bank

Data fields

All fields live inside data. Not every field is present for every bank - omitted fields are absent from the object rather than null.

FieldTypeBanksDescription
sender_name string All Full name of the payer / debit account holder.
sender_account string All Masked or partial account number of the sender.
receiver_name string All Full name of the beneficiary / credit account holder.
receiver_account string All Masked or partial account / phone of the receiver.
receiver_bank string awash, boa, cbe Destination bank name (inter-bank transfers).
sent_amount string All Amount credited to the receiver, with currency suffix.
service_charge string All Bank service / commission fee.
tax_vat string All 15% VAT on the service charge.
total_paid string All Total debited from sender including fees.
transfer_date string All Transaction timestamp. Format varies by bank.
transaction_id string All Unique transaction reference used for caching and deduplication.
transaction_type string boa, cbemobile, dashen, awash Type of transaction (transfer, topup, etc.).
reason string cbe, awash, boa, telebirr, mpesa Payment reason or narrative.
channel string telebirr, cbebirr Payment channel used.
invoice_no string zemen Invoice number.
receipt_number string cbebirr Receipt number.
order_id string cbebirr Order ID.
receipt_no string mpesa Receipt number.
service_type string dashen Service type label.
narrative string dashen Free-text narrative.
transfer_ref string dashen Secondary transfer reference.
error string All (when invalid) Human-readable reason when valid is false.
confirmation.confirmed_count integer All cached txns Number of times this transaction has been verified.
confirmation.first_confirmed_at datetime All cached txns ISO 8601 timestamp of the first verification.
confirmation.last_confirmed_at datetime All cached txns ISO 8601 timestamp of the most recent verification.
settlement_match.matched boolean With settlement_account true if the receiver account matches.
settlement_match.match_type string With settlement_account exact · suffix · masked_pattern · phone_normalised · unmatched.
settlement_match.match_confidence string With settlement_account high or none.

Supported institutions

Pass the bank code explicitly, or omit it and let url/reference auto-detect it where possible. Methods: reference = transaction reference, link = full receipt URL.

InstitutionCodeMethods
CBE Commercial Bank of Ethiopia
cbe reference · link
CBE-M CBE Mobile Receipt
cbemobile link · reference
CB CBE Birr
cbebirr reference (+ phone) · link
BoA Bank of Abyssinia
boa reference · link
TB Telebirr
telebirr reference · link
MP M-Pesa Ethiopia
mpesa reference · link
DS Dashen Super App
dashen link only
AW Awash Bank
awash link only
WG Wegagen Bank
wegagen link only
ZB Zemen Bank
zemen reference · link
NIB NIB International Bank
nib reference
SB Siinqee Bank
siinqee reference
AB Abay Bank
abay reference
KE Kaafi Ebirr
kaafiebirr reference
ZZ Zamzam Bank
zamzam reference · link

Rate limits

Only /v1/verify is rate-limited - every other endpoint is unlimited.

TierLimitScope
Anonymous (no bearer token)10 requests / hourPer IP address
Authenticated (any valid token)No limit-

Cached lookups still count against the anonymous limit. When exceeded, the API returns 429 Too Many Requests with a JSON body explaining the limit - no separate Retry-After header is set, so read error in the response rather than relying on headers.


Request examples

cbe Commercial Bank of Ethiopia
Full URL
POST /api/v1/verify
Content-Type: application/json

{"url": "https://apps.cbe.com.et:100/?id=FT25211G11JQ21827223"}
Reference + suffix
POST /api/v1/verify
Content-Type: application/json

{"reference": "FT25211G11JQ", "bank": "cbe", "suffix": "21827223"}
cbemobile CBE Mobile Receipt
Full URL
POST /api/v1/verify
Content-Type: application/json

{"url": "https://mbreciept.cbe.com.et/fHCxyUdnBt2PA0H8ge"}
Token as reference
POST /api/v1/verify
Content-Type: application/json

{"reference": "fHCxyUdnBt2PA0H8ge", "bank": "cbemobile"}
cbebirr CBE Birr
Full URL
POST /api/v1/verify
Content-Type: application/json

{"url": "https://cbepay1.cbe.com.et/aureceipt?TID=ABC1234567&PH=0911234567"}
Reference + phone
POST /api/v1/verify
Content-Type: application/json

{"reference": "ABC1234567", "bank": "cbebirr", "phone": "0911234567"}
boa Bank of Abyssinia
Full URL
POST /api/v1/verify
Content-Type: application/json

{"url": "https://cs.bankofabyssinia.com/api/onlineSlip/getDetails/?id=FT26166XLKCN05677"}
Reference + suffix
POST /api/v1/verify
Content-Type: application/json

{"reference": "FT26166XLKCN", "bank": "boa", "suffix": "05677"}
telebirr Telebirr
Full URL
POST /api/v1/verify
Content-Type: application/json

{"url": "https://transactioninfo.ethiotelecom.et/receipt/DF98Q6QQRM"}
Reference (auto-detect)
POST /api/v1/verify
Content-Type: application/json

{"reference": "DF98Q6QQRM"}
zemen Zemen Bank
Full URL
POST /api/v1/verify
Content-Type: application/json

{"url": "https://share.zemenbank.com/rt/94497018108ATWR2520600HM/pdf"}
Reference (auto-detect)
POST /api/v1/verify
Content-Type: application/json

{"reference": "94497018108ATWR2520600HM"}
awash Awash Bank
Full URL (required)
POST /api/v1/verify
Content-Type: application/json

{"url": "https://awashpay.awashbank.com:8225/-E41AE0D86FFA-21XYYW"}
dashen Dashen Super App
Full URL (required)
POST /api/v1/verify
Content-Type: application/json

{"url": "https://api.dashensuperapp.com/receipts/TopUp--Ethio-telecom-air-time-TopUp-387ETAP2522000WK.pdf"}
mpesa M-Pesa Ethiopia
Full URL
POST /api/v1/verify
Content-Type: application/json

{"url": "https://m-pesabusiness.safaricom.et/api/receipt/getReceipt?trxNo=MP12345ABC"}
Reference + bank
POST /api/v1/verify
Content-Type: application/json

{"reference": "MP12345ABC", "bank": "mpesa"}

Errors

Most verification outcomes - including invalid or not-found transactions - are still a 200 OK with valid: false; check status and data.error rather than the HTTP code. These are the actual non-200 responses across the API:

HTTPMeaningDescription
401 Unauthenticated Missing or invalid bearer token on an endpoint that requires one - everything except /v1/verify, /auth/register, and /auth/login.
404 Not found Token ID or saved-account ID in the URL doesn't belong to the caller.
422 Validation error On /v1/verify: neither url nor reference given, bank couldn't be auto-detected, or cbebirr is missing phone. On /auth/*: standard Laravel field validation (e.g. email already taken, password confirmation mismatch, wrong login credentials).
429 Rate limited /v1/verify only. Anonymous callers exceeded 10 requests/hour - see Rate limits.
502 Parse error /v1/verify only. The bank API returned something unparseable. Non-retriable - a different reference/URL is needed.

PDF receipts (CBE, CBE Birr, Dashen, Zemen, M-Pesa) are parsed with smalot/pdfparser - a pure-PHP library with no system dependencies.


Register & login

Both endpoints are public and return a personal access token to use as a bearer token on every other endpoint.

Register

POST /auth/register
Request body
{
  "name":                  "Abebe Kebede",
  "email":                 "abebe@example.com",
  "password":              "secret123",
  "password_confirmation": "secret123"
}
201 Created
{
  "user": { "id": 1, "name": "Abebe Kebede", "email": "abebe@example.com" },
  "token": "LNjMOFktw2rGV5F8u7uAxxxxxxxxxxxxxxxxxxxx"
}

Log in

POST /auth/login

Body: email, password. Returns the same { user, token } shape as register, or 422 with "The provided credentials are incorrect."

Current user

GET /auth/me auth required

Returns { id, name, email } for the authenticated token.

Log out

POST /auth/logout auth required

Revokes the token used on the request. Returns { "message": "Logged out." }.


Personal access tokens

Issue additional tokens (e.g. one per device or integration) without re-authenticating. All three routes require a bearer token.

MethodPathDescription
GET/auth/tokensList this user's tokens - id, name, last_used_at, created_at, expires_at (no plaintext token value).
POST/auth/tokensBody: { "name": "my-device" }. Returns 201 with the plaintext token - shown only this once.
DELETE/auth/tokens/{id}Revokes one token by id. 404 if it doesn't belong to the caller.

Saved accounts

A user's own address book of beneficiary accounts - handy for pre-filling settlement_account on repeat payments. All routes require a bearer token and are scoped to the caller; there's no cross-user access.

MethodPathDescription
GET/accountsList saved accounts, sorted by name.
POST/accountsBody: name, account_number, provider (all required). Returns 201.
PUT/accounts/{id}Same fields, all optional - only supplied ones are updated.
DELETE/accounts/{id}Removes the saved account. 404 if it doesn't exist for this caller.
200 OK · GET /accounts
[
  {
    "id": 3,
    "name": "Rent",
    "account_number": "1000123456789",
    "provider": "cbe",
    "created_at": "2026-06-01T09:12:00.000000Z"
  }
]
Have questions? Talk to us →