The public receipt URL each bank serves, what goes into it and what comes back. Generated from the same manifest the verifier uses, so it changes when the code changes.
Only live banks are verified; in-development banks are documented here but cheki does not check their receipts yet. Daily endpoint health is on the status page (GET /api/health gives a reachability snapshot, cached for 5 minutes).
Calling cheki instead of the banks? See the API docs. Requests are rate limited per IP (verify 30 per minute, batch 5 per minute with up to 20 receipts each); over the limit you get a 429 with a Retry-After header.
Receipt ID or mbreciept.cbe.com.et link. Shared from the CBE app as a link or QR code. Tokens look like fHCx... (v1) or v2-... (v2). No account number is needed. The API needs the CBE web app's X-App-ID/X-App-Version headers.
https://mbreciept.cbe.com.et/{RECEIPT_ID}
cheki reads it from https://mb.cbe.com.et/api/v1/transactions/public/transaction-detail/{ref}.
The CBE Receipt ID: the last part of the receipt link the CBE app shares.
Where to find it
Share the receipt from the CBE app, or scan its QR code. The link is mbreciept.cbe.com.et/{Receipt ID}. cheki reads it through CBE's JSON API at mb.cbe.com.et.
Length and pattern
Opaque token, letters and digits: fHCx... on v1 receipts, v2-... on newer ones. Length varies (about 18 to 24 characters).
Traps
It is not the FT reference. An FT reference sent to this API answers "Invalid or tampered legacy token".
Gotchas
The JSON API needs the CBE web app's X-App-ID and X-App-Version headers (and cheki also sends its Origin and Referer). Without them it answers HTTP 400 "Missing Headers".
Account numbers come back masked (1****1234).
A Receipt ID can't be derived from an FT reference, so an older receipt with only an FT reference needs the legacy format below.
Last seen: 500 application/problem+json "Security Alert: Invalid or tampered legacy token!"
Older receipts1 format
Most receipts use the current format above.
FT reference + last 8 digits of the receiving accountStill checked · inferred
https://apps.cbe.com.et:100/?id={ref}{account}
Needs: FT reference + Receiving account (last 8 digits). Response: PDF. Replaced by the current format. cheki still checks it. Older receipts and SMS that only show an FT reference. Also reached from dashed mbreciept links (mbreciept.cbe.com.et/FT...-12345678), which carry the FT reference and the 8-digit suffix (link shape inferred from links.et).
Evidence: Status inferred: links.et reports apps.cbe.com.et unreachable since July 2026; cheki's egress could not connect on 2026-10-11 (CONNECT 502), while a repo probe on 2026-10-10 got the 404 'You Are Not Allowed to See This Data' page. Not proven shut down, so cheki still tries it.
Last check: Endpoint answers in the expected shape. ()
{REFERENCE}
The CBE FT transaction reference.
Where to find it
On older receipts as "Reference No. (VAT Invoice No)", and in the CBE SMS.
Length and pattern
12 characters: "FT" plus 10 letters and digits, e.g. FT26140P01YB.
Traps
None known
{LAST_8_DIGITS_OF_ACCOUNT}
The last 8 digits of the receiving (credit) account.
Where to find it
The receiver's account number on the receipt, or your own account if you received the money.
Length and pattern
8 digits, appended directly after the reference with no separator.
Traps
The payer's account does not work. Only the receiving account matches.
Unknown reference: HTTP 404 text/html, "You Are Not Allowed to See This Data, Please Check your Link" (seen 2026-10-10; the host is often unreachable).
Changelog4 changes
cheki leads with the Receipt ID; FT reference + account is kept as a legacy format. cheki's egress could not connect to apps.cbe.com.et:100.
Repo probe: apps.cbe.com.et:100 answered an unknown id with HTTP 404 'You Are Not Allowed to See This Data'.
2026-07links.et reports the apps.cbe.com.et:100 PDF host unreachable since July 2026 and resolves CBE receipts through the JSON host.
cheki added CBE share-link receipts (mbreciept.cbe.com.et, read through the mb.cbe.com.et JSON API).
In the Telebirr SMS sent to payer and receiver, and on the in-app receipt. The receipt's Payment QR Code holds it too: scan or upload the QR and cheki reads the number out of it.
Length and pattern
10 characters: a 2 or 3 letter prefix (DET, CHQ, DAB, DJA, DEL, ADQ, ...) plus letters and digits, e.g. DET8FJGUJ4.
Traps
None known
Gotchas
Geo-restricted: only Ethiopian IP addresses get an answer. CI runners abroad mark it as not checked.
Geo-blocked: requests from outside Ethiopia time out or are refused. cheki shows the official link as a fallback.
The receipt's Payment QR Code is base64 of a hex string holding TLV data: tag 81 contains tag 00, the transaction number in ASCII; tag 63 is a CRC-16/CCITT-FALSE over the characters before it, case-sensitive. It holds nothing else cheki uses (no amount, no names), so cheki only takes the transaction number from it and checks that like a typed one. A QR is never proof of payment. Decoder: src/lib/qr/telebirr.ts, write-up: docs/telebirr-qr.md.
Response and fieldsHTML · 22 fields
Field
Meaning
senderName
Payer's name
senderAccount
Payer's account or phone (often masked)
receiverName
Receiver's name
receiverAccount
Receiver's account or phone (often masked)
amount
Amount transferred
currency
Currency, ETB
date
Transaction time as the bank prints it
reference
The bank's own reference
invoiceNumber
Invoice number
transactionStatus
Status as the bank reports it
settledAmount
Bank-specific field
stampDuty
Bank-specific field
discountAmount
Bank-specific field
serviceFee
Service fee
serviceFeeVat
VAT on the service fee
totalPaid
Total paid, fees included
amountInWords
Bank-specific field
paymentMode
Payment mode
paymentChannel
Channel (app, USSD, agent)
reason
Payment reason or narrative
bankAccountNumber
Bank-specific field
bankAccountName
Bank-specific field
Unknown-reference error shape
Not checked from CI. The endpoint only answers Ethiopian IP addresses.
Changelog1 change
cheki reads the Payment QR Code on Telebirr receipts (base64 → hex → TLV, tag 81/00 = transaction number, CRC-16/CCITT-FALSE checked) and checks the number like a typed reference. Before this the QR was not recognised. DJA added to the known transaction-number prefixes.
Beta. M-Pesa's receipt format is taken from other open-source verifiers. cheki has not yet verified a real M-Pesa receipt, so treat a result as provisional until one confirms the parser.
The M-Pesa Ethiopia transaction ID (trxNo), printed on the receipt as TRANSACTION ID.
Where to find it
In the M-Pesa SMS confirmation and on the receipt.
Length and pattern
Letters and digits, usually 10 characters (Safaricom style, e.g. UAB1CD2EF3).
Traps
None known
Gotchas
Geo-restricted: only Ethiopian IP addresses get an answer. CI runners abroad mark it as not checked.
Geo-blocked: requests from outside Ethiopia time out or are refused.
The answer is JSON, but the receipt itself is a PDF in base64Data. cheki checks the base64, caps the decoded PDF at 5 MB, and reads the PDF text (TRANSACTION ID, SENDER or PAYER NAME, RECEIPT NO / PAYMENT DATE / SETTLED AMOUNT, TOTAL, ...).
Beta: the envelope and the PDF labels come from other open-source verifiers' source code. cheki's tests use a synthetic receipt; no real receipt has been checked yet.
Other verifiers send a Referer of https://m-pesabusiness.safaricom.et/; one notes the endpoint requires it. cheki sends it too.
Response and fieldsJSON envelope with the receipt as a base64 PDF (inferred, beta) · 13 fields
Field
Meaning
reference
The bank's own reference
senderName
Payer's name
senderAccount
Payer's account or phone (often masked)
receiverName
Receiver's name
receiverAccount
Receiver's account or phone (often masked)
amount
Amount transferred
settledAmount
Bank-specific field
serviceFee
Service fee
serviceFeeVat
VAT on the service fee
totalPaid
Total paid, fees included
date
Transaction time as the bank prints it
transactionType
Bank-specific field
paymentChannel
Channel (app, USSD, agent)
Unknown-reference error shape
Not checked from CI (geo-blocked). Other verifiers treat any responseCode other than "0", or a missing base64Data, as no receipt; the exact error code and wording are not known.
Changelog3 changes
cheki reads the envelope and its PDF instead of flat JSON fields (which no source shows the endpoint returning). M-Pesa labelled beta until a real receipt verifies.
Parser reads the base64 PDF envelope instead of flat JSON fields. Labelled beta until a real receipt verifies.
Vixen878/verifier-api adds M-Pesa: getReceipt returns {responseCode, base64Data}; the PDF is parsed for PAYER NAME, TRANSACTION ID, TOTAL and more.
In the Dashen app it is labelled "FT Ref". On the web receipt it is labelled "Transaction Reference".
Length and pattern
16 characters, letters and digits, e.g. B22WDTI2619100WH.
Traps
The app also shows a longer "Transaction Ref" or "Transfer Reference" (WDTI... or OBTI...). The endpoint rejects it. Use the FT Ref.
Gotchas
The old host receipt.dashensuperapp.com now returns 502 Bad Gateway. Old PDF links on api.dashensuperapp.com are still detected and routed to the new host.
The live endpoint returns an HTML receipt, not a PDF. GET /api/receipt redirects to it rather than serving the bank's HTML from cheki.
Response and fieldsHTML · 9 fields
Field
Meaning
senderName
Payer's name
senderAccount
Payer's account or phone (often masked)
receiverName
Receiver's name
receiverAccount
Receiver's account or phone (often masked)
amount
Amount transferred
currency
Currency, ETB
date
Transaction time as the bank prints it
reference
The bank's own reference
reason
Payment reason or narrative
Unknown-reference error shape
HTTP 400 application/json, {"statusCode":400,"message":"Transaction not found"}.
Last seen: 400 application/json "Transaction not found"
Older receipts1 format
Most receipts use the current format above.
Dashen Super App receipt linkRetired
https://receipt.dashensuperapp.com/receipt/{ref}
Needs: Old receipt link. Response: PDF. Retired 2026-10-10. Replaced by the current format. cheki never fetches it; links are read for their reference only. Never fetched. Old links (receipt.dashensuperapp.com/receipt/{ref}, api.dashensuperapp.com/receipts/Within-Dashen-Transfer-{ref}.pdf) are still recognised: cheki reads the FT Ref from them and checks it on receipts.dashenbanksc.com.
Evidence: receipt.dashensuperapp.com answered 502 Bad Gateway (cheki, 2026-10-10).
Changelog2 changes
/api/receipt redirects to Dashen's own receipt page instead of proxying its HTML.
Moved from receipt.dashensuperapp.com (PDF, now 502) to receipts.dashenbanksc.com (HTML). The FT Ref is the only accepted reference.
The token in a Zemen share link, share.zemenbank.com/rt/{token}/pdf.
Where to find it
Use the share button on the Zemen receipt and paste the whole link. cheki reads the token from the path.
Length and pattern
Unconfirmed. links.et docs describe a 24 character token (an 8 character prefix plus the 16 character reference). Another open-source app builds the URL from the bare reference. Neither is proven.
Traps
A bare reference may not work. Paste the full share link until a real receipt settles the format.
Gotchas
The parser labels come from the expected PDF layout and are not tested on a real receipt yet.
Response and fieldsPDF (expected) · 10 fields
Field
Meaning
senderName
Payer's name
senderAccount
Payer's account or phone (often masked)
receiverName
Receiver's name
receiverAccount
Receiver's account or phone (often masked)
amount
Amount transferred
currency
Currency, ETB
date
Transaction time as the bank prints it
reference
The bank's own reference
reason
Payment reason or narrative
transactionStatus
Status as the bank reports it
Unknown-reference error shape
HTTP 404 application/json, {"messages":"Transaction not found.","status":"404"}.
Last seen: 404 application/json "Transaction not found."
Changelog1 change
Marked in development until a real receipt confirms the token format and PDF layout.
On the CBE Birr receipt and in the confirmation SMS.
Length and pattern
10 letters and digits according to another open-source verifier's parser. Unconfirmed here.
Traps
None known
{PAYER_PHONE}
The phone number of the wallet that paid.
Where to find it
Ask the payer, or read it from the SMS.
Length and pattern
12 digits starting with 2519, e.g. 2519XXXXXXXX.
Traps
The receiver's phone does not work.
Gotchas
A miss still returns HTTP 200 (the Telerik shell). Only the labels tell a real receipt apart.
Another open-source verifier reads real receipts as a PDF with labels Debit Account, Credit Account, Receiver Name, Order ID, Transaction Status, Reference, Receipt Number, Paid amount, Service charge, VAT, Total Paid Amount, Payment Reason and Payment Channel. cheki accepts PDF or HTML until a real receipt confirms which.
Response and fieldsPDF or HTML (unconfirmed) · 14 fields
Field
Meaning
senderName
Payer's name
senderAccount
Payer's account or phone (often masked)
receiverName
Receiver's name
receiverAccount
Receiver's account or phone (often masked)
orderId
Order ID
transactionStatus
Status as the bank reports it
reference
The bank's own reference
date
Transaction time as the bank prints it
amount
Amount transferred
serviceFee
Service fee
serviceFeeVat
VAT on the service fee
totalPaid
Total paid, fees included
reason
Payment reason or narrative
paymentChannel
Channel (app, USSD, agent)
Unknown-reference error shape
HTTP 200 text/html: an empty Telerik ReportViewer shell titled "Auto Receipt" with no transaction data.
Last seen: 200 text/html "Auto Receipt"
Changelog4 changes
Request timed out from cheki's egress.
Unknown TID/PH returns HTTP 200 with an empty Telerik ReportViewer shell.
Marked in development until tested on a real receipt.
Parser reads PDF or HTML using the real receipt labels.
The Siinqee receipt token on the shared eBirr platform.
Where to find it
The receipt link in the SMS. It is the last path segment.
Length and pattern
Alphanumeric, length not yet confirmed.
Traps
None known
Gotchas
On 2026-10-10 the siinqee route answered HTTP 404 text/plain "Cannot GET /siinqee/...", unlike other eBirr tenants. The tenant name may have changed. A real Siinqee receipt link would settle it.
Response and fieldsHTML · 8 fields
Field
Meaning
senderName
Payer's name
senderAccount
Payer's account or phone (often masked)
receiverName
Receiver's name
receiverAccount
Receiver's account or phone (often masked)
amount
Amount transferred
currency
Currency, ETB
date
Transaction time as the bank prints it
reference
The bank's own reference
Unknown-reference error shape
Expected like other eBirr tenants: HTTP 200 text/html "Not Found Page".
Last seen: 404 text/plain "Cannot GET /siinqee/chekiprobe000000"
Also reported, not supported yet1
Siinqee Bank receipt linkNewer, not supported yet · inferred
Host: receipts.siinqeebank.com
Needs: Receipt link. Response: PDF. cheki does not read it yet. Not fetched. Needs a real receipt link and a parser that respects the view limit.
Evidence: links.et lists receipts.siinqeebank.com:871 (PDF) for Siinqee Bank and says each receipt allows about five views in total; it showed Siinqee unavailable on 2026-10-11. Not tested by cheki and not supported yet.
Changelog2 changes
links.et lists Siinqee Bank receipts on receipts.siinqeebank.com:871 (PDF). Recorded as inferred, not supported.
We have not found a public receipt link for this bank. A shared receipt link or QR code is the fastest way to find one.
Reported host: receipt.amharabank.com.et, transaction.amharabank.com.et. links.et lists receipt.amharabank.com.et, transaction.amharabank.com.et (JSON). Not tested by cheki and not supported yet.
Make a small payment to yourself and open the bank's share or download option on the receipt. Note the link it gives you, or scan the QR code with any reader to get its text.
Find the variable parts of the link: which segment is the reference, and whether an account suffix, phone number or token is needed. Compare two receipts to see what changes.
Send a fake reference in the same shape and record the status code, content type and body. That is the unknown-reference shape the health check will watch.
Check whether the endpoint answers from outside Ethiopia. If not, mark it geo-blocked in the manifest.
Run npm run probe -- <your link> locally to see what the current detector and parsers make of it, without sending the receipt to anyone.
Open a bank request with the host and a redacted pattern only. Never paste a full receipt link in a public issue. If you are happy to share the real link, use the private box on the bank's page.
To write the parser yourself, follow Adding or fixing a bank: a manifest entry, a parser, a redacted fixture and a test.