Odit Verify

CBE

Detail page — four URL shapes (two short-link variants and two direct endpoints), two response schemas (PDF and JSON), and how the resolver routes between them.

CBE has the busiest URL surface of any provider — four supported shapes that resolve to one of two upstream backends. Pass any of them; the resolver figures out the right call.

URL formats

https://mbreciept.cbe.com.et/<id>           # id contains a "-"
PartPatternExample
hostliteralmbreciept.cbe.com.et
<id>FT[A-Z0-9]+-\d+FT00000000-12345678

Resolution: dash is stripped → call apps.cbe.com.et:100/?id=<stripped> which returns a PDF. We parse with pdf-parse.

https://mbreciept.cbe.com.et/<token>        # no dash, mixed case
PartPatternExample
<token>16+ [A-Za-z0-9], at least one lowercasefHCxz62uM8157lCfet

Resolution: passed through to mb.cbe.com.et/api/v1/transactions/public/transaction-detail/<token> which returns JSON.

3. Direct PDF

https://apps.cbe.com.et:100/?id=<id>
https://apps.cbe.com.et:100/BranchReceipt/<id>

Pass-through. Returns a PDF.

4. Direct JSON

https://mb.cbe.com.et/api/v1/transactions/public/transaction-detail/<id>

Pass-through. Returns JSON.

Return types

CBE has two response shapes, depending on which backend the URL resolves to. The source field discriminates: present ("mb-json") for the JSON backend, absent for the PDF backend (the legacy parser).

PDF — source is absent

interface CbePdfReceipt {
  // No `source` field — discriminate by its absence + providerKey === 'cbe'
 
  // Counterparties (mostly strings — names + masked accounts)
  payerName?: string;
  payerAccount?: string;             // masked: "1****0000"
  receiverName?: string;
  receiverAccount?: string;          // masked
 
  // Transaction
  paymentDate?: string;              // free text, e.g. "1/1/2026, 00:00:00 AM"
  reference?: string;                // FT-prefixed id
  paymentReason?: string;            // free text
 
  // Amounts (numeric — already parsed)
  transferredAmount?: number;
  serviceCharge?: number;
  vat?: number;
  totalAmount?: number;
  currency?: string;                 // "ETB" | "USD" | …
  amountInWords?: string;
 
  // Customer block (receipt holder profile)
  customerName?: string;
  branch?: string;
 
  // Company-info block (CBE's own VAT registration)
  vatReceiptNo?: string;
  vatRegistrationNo?: string;
  vatRegistrationDate?: string;
}

JSON — source: "mb-json"

Same base fields as PDF, plus everything mb.cbe.com.et returns that the PDF doesn't expose.

interface CbeMbJsonReceipt {
  source: 'mb-json';
 
  // Shared with PDF
  payerName?: string;
  payerAccount?: string;
  receiverName?: string;
  receiverAccount?: string;
  paymentDate?: string;              // ISO 8601, e.g. "2026-01-01T00:00:00Z"
  reference?: string;
  paymentReason?: string;
  transferredAmount?: number;
  serviceCharge?: number;
  vat?: number;
  totalAmount?: number;
  currency?: string;
  vatReceiptNo?: string;
 
  // JSON-only enrichments
  transactionType?: string;          // e.g. "ACNX"
  processingDate?: string;           // "YYYYMMDD"
  debitValueDate?: string;
  creditValueDate?: string;
  chargeCode?: string;               // e.g. "WAIVE"
  debitTheirRef?: string;
  creditTheirRef?: string;
 
  commissions?: Array<{ type: string; amount: number; currency: string }>;
  taxes?: Array<{ type: string; amount: number; currency: string }>;
  paymentDetails?: string[];         // free-text notes
  creditAmount?: number;             // usually equal to debitAmount minus rounding
}

Sample — PDF backend (placeholder values)

{
  "payerName": "<payer name>",
  "payerAccount": "1****0000",
  "receiverName": "<receiver name>",
  "receiverAccount": "1****0000",
  "paymentDate": "1/1/2026, 00:00:00 AM",
  "reference": "FT00000000",
  "paymentReason": "Account Transfer",
  "transferredAmount": 100,
  "serviceCharge": 1.74,
  "vat": 0.26,
  "totalAmount": 102,
  "currency": "ETB",
  "amountInWords": "<amount in words>",
  "customerName": "<receipt holder>",
  "branch": "<branch>",
  "vatReceiptNo": "ABC0000",
  "vatRegistrationNo": "0000",
  "vatRegistrationDate": "1/1/2020"
}

Sample — JSON backend (placeholder values)

{
  "source": "mb-json",
  "payerName": "<payer name>",
  "payerAccount": "1********0000",
  "receiverName": "<receiver name>",
  "receiverAccount": "1********0000",
  "paymentDate": "2026-01-01T00:00:00Z",
  "reference": "FT00000000",
  "paymentReason": "Mobile Banking Transfer",
  "transferredAmount": 260,
  "serviceCharge": 0.61,
  "vat": 0.08,
  "totalAmount": 260.61,
  "currency": "ETB",
  "vatReceiptNo": "FT00000000",
  "transactionType": "ACNX",
  "processingDate": "20260101",
  "chargeCode": "WAIVE",
  "commissions": [{ "type": "COMFTMB", "amount": 0.5, "currency": "ETB" }],
  "taxes":       [{ "type": "15",     "amount": 0.08, "currency": "ETB" }],
  "paymentDetails": ["Mobile Banking Transfer"],
  "creditAmount": 260
}

Quirks

  • mb.cbe.com.et is gated — without the right x-app-id, x-app-version, Referer, Origin, and User-Agent, CBE returns 500 Security Alert: Invalid or tampered legacy token!. v.odit.et sends the full browser-equivalent header set on every JSON request.
  • PDF body has positional-only fields — in the company-info block, labels are stacked first and values stacked second. Empty cells are omitted from values, which makes positional pairing fragile. We extract only the last three values (vatReceiptNo / vatRegistrationNo / vatRegistrationDate) which are stable across receipts.
  • Two amount string formatsmb.cbe.com.et sometimes prefixes amounts with the currency ("ETB0.50"), sometimes returns bare decimals ("260.00"). Our parser handles both.
  • PDF endpoint runs on port 100 — non-standard, but is the official URL. Direct port 443 doesn't work.

On this page