Documentation

API reference

Base URL: https://verify.qbirr.com

Stable since v1. Backwards-compatible changes are added regularly; breaking changes get a new path prefix.

Find your API key at /dashboard/keys. Treat it like a password — never expose it in browser code.

Authentication

Every request needs your API key in the X-API-Key header. The key looks like abc12345.LONGRANDOMSECRET — send the whole string.

POST /api/v1/verify

Verifies one transaction. Returns verified: true only if the receipt is real, the amount matches, and the receiver name matches.

curl -X POST https://verify.qbirr.com/api/v1/verify \
  -H "X-API-Key: $QBIRR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "cbe",
    "ref": "FT23001234ABC",
    "amount": 500,
    "receiver_name": "YOUR FULL NAME",
    "receiver_account": "1000123456789"
  }'

Request fields

Field Type Notes
providerstringcbe · telebirr · abyssinia · dashen · mpesa · awash · ebirr · zamzam
refstringThe reference, or the whole SMS you received, or the receipt link. All three work — we extract the reference ourselves. For awash the SMS URL or its token; for ebirr the URL or "tenant/token"
amountnumberExpected amount in ETB. Overpayment OK; underpayment up to 10 ETB tolerated
receiver_namestringOptional — but send it. Without an identity we can only confirm the reference exists and the amount matches, not who received the money. We check the receipt against this and report receiver_source accordingly
receiver_accountstringRequired for cbe and abyssinia when you have not saved the account in your dashboard

Response

{
  "verified":        true,
  "payer":           "JANE DOE",
  "amount":          500,
  "paid_amount":     500,
  "error":           "",
  "retryable":       false,
  "code":            null,
  "receiver_source": "settings"
}

HTTP is 200 whether the payment verified or not — always read verified, never the status code.

The three fields that stop you rejecting honest payments

Field What it means
retryable If this is true, do not reject the order. It means we could not reach the bank, so verified: false means not checked yet — not invalid. Retry, or fall back to manual review. Only false is a verdict on the payment.
code A stable, machine-readable reason. Branch on this instead of parsing error — see the table below.
paid_amount The amount the receipt actually shows. On a short payment, compare it to amount to see what arrived — 0 when we could not read it.
receiver_source Which receiver identity was actually checked. "settings" — your saved account in the dashboard (strongest). "request" — the receiver_name/receiver_account you sent. "none" — nothing was checked. A verified: true with "none" confirms the reference exists and the amount matches, but says nothing about who received the money.

Reason codes

code retryable Meaning
RECEIPT_NOT_FOUNDfalseThe bank answered and has no such reference.
AMOUNT_MISMATCHfalseThe receipt exists but the amount does not match. Read paid_amount.
RECEIVER_MISMATCHfalseThe receipt is made out to someone else.
RECEIPT_EXPIREDfalseOlder than the age limit, if your account has one configured.
RECEIPT_UNREADABLEfalseWe could not read the receipt the bank returned.
REFERENCE_INVALIDfalseThe reference/link/token is not a shape we can look up. Read error — it says exactly what to paste.
RECEIVER_ACCOUNT_REQUIREDfalseThis provider needs your account number to look the reference up.
UNSUPPORTED_INPUTfalseThat input form is not supported for this provider.
VERIFICATION_FAILEDfalseUnclassified. Still not verified, but no better reason.
DUPLICATE_REFERENCEfalseThis payment was already verified for your account, in whatever form it was submitted.
UPSTREAM_UNAVAILABLEtrueNot a verdict. We could not reach the bank. Retry — do not reject the order.

The request-level failures (MISSING_API_KEY, INVALID_API_KEY, ACCOUNT_SUSPENDED, NO_PLAN, PLAN_EXPIRED, QUOTA_EXCEEDED, INVALID_REQUEST, REQUEST_TOO_LARGE) also carry a code and come with a non-200 status.

Sandbox (test without a bank)

Any ref starting with TEST- returns a canned result without contacting a bank. It does not consume your quota, and the response carries "sandbox": true so you can never mistake a fixture for a real verdict. Use it to build and test your integration before a single real payment arrives.

# always verifies
curl -X POST https://qbirr.com/api/v1/verify -H "X-API-Key: $QBIRR_API_KEY" \
  -d '{"provider":"cbe","ref":"TEST-OK","amount":500}'

# and one for each failure you should handle
TEST-EXPIRED          -> RECEIPT_EXPIRED
TEST-WRONG-AMOUNT     -> AMOUNT_MISMATCH
TEST-NOT-FOUND        -> RECEIPT_NOT_FOUND
TEST-WRONG-RECEIVER   -> RECEIVER_MISMATCH
TEST-DUPLICATE        -> DUPLICATE_REFERENCE

GET /api/v1/usage

Returns the current calendar month's quota status.

curl https://verify.qbirr.com/api/v1/usage -H "X-API-Key: $QBIRR_API_KEY"

{
  "used": 124,
  "limit": 1000,
  "remaining": 876,
  "plan": "Starter",
  "period_start": "2026-05-01T00:00:00Z",
  "period_end":   "2026-06-01T00:00:00Z"
}

HTTP status codes

Code Meaning
200Processed. Check verified — could be true or false
400Malformed request
401Missing or invalid API key
402No active plan or plan expired
403Account suspended
429Quota or per-IP rate limit (60 req/min)
413Body or upload too large

Provider notes

C

CBE cbe

Commercial Bank of Ethiopia.

Requires receiver_account.

T

Telebirr telebirr

Ethio Telecom mobile money.

Geo-blocked to Ethiopian IPs — handled automatically by our relay.

A

Awash Bank awash

Private bank.

Paste the SMS URL or just the URL token as ref.

D

Dashen Bank dashen

Super App.

M

M-Pesa mpesa

Safaricom Ethiopia.

Geo-blocked to Ethiopian IPs — handled automatically by our relay.

B

Bank of Abyssinia abyssinia

Private bank.

Requires receiver_account.

E

eBirr ebirr

COOPay · KAAFI · Nib · Wegagen · Ahadu.

Paste the URL or "tenant/token" as ref.

Z

ZamZam Bank zamzam

Private bank.

B

CBE Birr cbebirr

CBE mobile wallet.

Paste the whole SMS, the receipt link …/aureceipt?TID=…&PH=…, or just the Txn ID.

Official SDKs

Node.js

Node 18+ · zero deps

npm install git+https://git.qbirr.com/admin/sdk-node.git

Python

Python 3.8+ · requests

pip install git+https://git.qbirr.com/admin/sdk-python.git

PHP

PHP 7.4+ · curl + json

composer require git+https://git.qbirr.com/admin/sdk-php.git

Go

Go 1.21+ · stdlib only

go get git.qbirr.com/admin/sdk-go@v1.0.0

For Ruby, .NET, Java, Rust, or anything else — see the cURL example above; it's the same HTTP shape everywhere.

Best practices