Skip to main content

Getting Started

Integrate Ethiopian Transaction Verification

Create an API key, register a webhook, and submit a verification. Verify.et sends the finished result to your server. Use polling or SSE only when a screen needs to show progress.

WebhooksAPI keysReceipt imagesPollingSSEBank-specific fields

Choose an Integration Flow

Webhooks

Suggested. Register one HTTPS endpoint. Verify.et sends the finished result even if the customer closes the page.

Jump to Section
Polling or SSE

Use this when a screen should update while the check is still running. Keep a webhook for the final result.

Jump to Section
Short Wait

Submit with waitMs for a best-effort immediate answer. Still handle 202, and still use a webhook.

Jump to Section
Receipt image

Send a photo or screenshot. Verify.et reads the QR code, then the printed fields, and verifies that payment.

Jump to Section

Create an API Key

  1. 1

    Sign in

    Open /login and authenticate with your account.
  2. 2

    Create a key

    Create a key and save the full VERIFY_BANK_ET_your_key_here secret. It is shown once.Open API Keys
  3. 3

    Set permissions

    For verification flows, include verification:write to submit requests and verification:read to poll, stream, or read history.
  4. 4

    Send the header

    Add x-api-key to every API request. Add Idempotency-Key to safely retry submissions.

Next, add a webhook

Register one URL. Verify.et sends the finished result there.

Open Webhooks

Credits are account-level

API keys authenticate and rate-limit requests. Verification requests use the same account credits as dashboard, mobile, and app usage. Handle 429 and 402 responses as recoverable integration states.

Receive Results With a Webhook

Register one public HTTPS URL. Verify.et sends verification.completed when a check finishes.

  1. 1

    Add the endpoint

    Open Webhooks, add your URL, and copy the whsec_ secret. It is shown once.Open Webhooks
  2. 2

    Send a test

    Click Send test. A 2xx response makes the endpoint active. Sign timestamp, a period, and the raw body with HMAC-SHA256, then reply 204.
  3. 3

    Submit a verification

    Call POST /api/verify with your API key. On 202, store requestId. The webhook brings the result.
curl -X POST \
  "https://verify.et/api/verify" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  -H "Idempotency-Key: checkout-1042" \
  -d '{
    "bank": "telebirr",
    "transactionNumber": "TELE123456"
  }'

Default event

verification.completed arrives for every finished check: verified, not found, or failed. Extra events are separate messages and stay off unless you turn them on.

Headers

X-Webhook-Event, X-Webhook-Event-Id, X-Webhook-Timestamp, X-Webhook-Delivery-Id, and X-Webhook-Signature. Test pings set X-Webhook-Test: true.

Retries

Up to 5 attempts: immediately, then after 30 seconds, 2 minutes, 8 minutes, and 30 minutes. Retries reuse the event id and JSON. The timestamp is new, so check the signature again, then skip work you already finished.

Already sending webhookUrl?

A per-request webhookUrl or X-Webhook-Url still works and uses the older platform secret. New integrations should use the dashboard endpoint instead.

Use the Production Endpoint

Verify.ET is provided through the hosted verify.et domain. These examples intentionally use the same base URL your production integration uses.

Base URL
VERIFY_ET_BASE_URL=https://verify.et
VERIFY_ET_API_KEY=VERIFY_BANK_ET_your_key_here

Submit Your First Verification

POST /api/verify accepts explicit bank payloads and universal smart-router payloads. Leave webhookUrl out. An active dashboard endpoint already receives the result. Identifier fields may include full SMS text or supported receipt URLs; the server best-effort normalizes them when confident (explicit bank is never re-routed). Prefer clean fields in production. waitMs asks the API to wait briefly for a terminal result, but the server caps the wait time per bank. A photo of the receipt uses the steps in Verify from a receipt image.

curl -X POST \
  "https://verify.et/api/verify?waitMs=5000" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  -H "Idempotency-Key: verify-demo-001" \
  -d '{
    "bank": "cbe",
    "reference": "fHCxyExampleRcpt1"
  }'

Handle both 200 and 202

A 200 response includes the completed verification. A 202 response means the request is queued. Store the requestId. Your webhook receives the finished result, including when the customer leaves the page.
Queued Response Shape
{
  "success": true,
  "message": "Verification queued.",
  "data": [],
  "requestId": "550e8400-e29b-41d4-a716-446655440000",
  "statusUrl": "/api/verify/550e8400-e29b-41d4-a716-446655440000",
  "estimatedWaitMs": 5000,
  "verification": {
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "bank": "cbe",
    "processingStatus": "queued",
    "status": "pending",
    "verified": false
  },
  "links": {
    "statusUrl": "/api/verify/550e8400-e29b-41d4-a716-446655440000",
    "pollAfterMs": 1500
  }
}

Verify From a Receipt Image

Optional

Send a photo on the same POST /api/verify request. Put the file in one part named image.

  1. 1

    Turn it on

    Image Processing starts off. The billing owner turns on the switch.Enable Image Processing
  2. 2

    Add image credits

    One accepted photo spends one image credit. Packs start at 1,000 credits for 500 ETB.Top up image credits
  3. 3

    Send the photo

    Post the file with x-api-key and Idempotency-Key. Use JPEG, PNG, or WebP, up to 8 MB. 200 is finished and 202 is queued. Read imageProcessing on the response.
curl -X POST \
  "https://verify.et/api/verify?waitMs=5000" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  -H "Idempotency-Key: receipt-order-123" \
  -F "[email protected];type=image/jpeg" \
  -F "bank=cbe" \
  -F "reference=fHCxyExampleRcpt1"
Accepted image response
{
  "success": true,
  "message": "Verification queued.",
  "requestId": "550e8400-e29b-41d4-a716-446655440000",
  "imageProcessing": {
    "attemptId": "img_018f2c4e-9b1a-7d33-a6c0-e5b4c8d21a90",
    "method": "qr",
    "chargedCredits": 1
  },
  "verification": {
    "processingStatus": "queued",
    "status": "pending",
    "verified": false
  },
  "links": {
    "statusUrl": "/api/verify/550e8400-e29b-41d4-a716-446655440000",
    "pollAfterMs": 1500
  }
}

One image credit per accepted file

A valid file spends one image credit, including when reading or verification later fails. An empty, oversized, or undecodable file spends none, and so does a missing verification credit. Repeating the same key with the same file and fields spends nothing more. A corrected photo needs a new key.
Field list, response, and error codes

Poll or Stream While You Wait

Use this when a page should show progress. Poll GET /api/verify/:requestId until processingStatus is completed or failed, for up to 125 seconds. Do not submit the same receipt again while that request is in flight. Keep the webhook you registered above so the result still arrives if the customer leaves.

Polling Loop
const baseUrl = process.env.VERIFY_ET_BASE_URL ?? "https://verify.et";
const requestId = process.env.VERIFY_ET_REQUEST_ID!;
const statusUrl = `${baseUrl}/api/verify/${encodeURIComponent(requestId)}`;

const deadline = Date.now() + 125_000;
while (Date.now() < deadline) {
  const response = await fetch(statusUrl, {
    headers: { "x-api-key": process.env.VERIFY_ET_API_KEY! },
  });
  const body = await response.json();
  const status = body.data?.processingStatus;

  if (status === "completed" || status === "failed") {
    return body.data;
  }

  await new Promise((resolve) => setTimeout(resolve, body.links?.pollAfterMs ?? 1500));
}

throw new Error("Verification did not finish within 125 seconds.");
SSE Stream
VERIFY_ET_BASE_URL="https://verify.et"
VERIFY_ET_REQUEST_ID="replace-with-the-request-id-from-submit"

curl -N \
  -H "Accept: text/event-stream" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  "$VERIFY_ET_BASE_URL/api/verify/$VERIFY_ET_REQUEST_ID/events"

SSE event names

The stream sends status events as the row changes, then a done event with reason terminal or timeout.

Bank Fields

Use universal fields when you want the router to detect the provider. Use explicit bank fields when your checkout already knows the bank or wallet.

Universal Payload

FieldRequiredDescription
bankOptionalOptional for universal routing. Use it when you already know the provider and want strict validation.
referenceOptionalUniversal input. For CBE, put the Receipt ID here. It can also be a bank reference, transaction number, receipt URL, or token.
suffixOptionalAccount suffix for older FT references only. Use 8 digits for a legacy CBE FT reference or 5 digits for Bank of Abyssinia. A CBE Receipt ID does not use a suffix.
phoneNumberOptionalUniversal CBE Birr phone. Accepts 09XXXXXXXX or 251XXXXXXXXX and normalizes to 251XXXXXXXXX.
webhookUrlOptionalOptional one-off callback. Prefer an active dashboard webhook endpoint, then leave this field out.

Explicit Bank Payloads

FieldRequiredDescription
cbeRequiredSend the Receipt ID in reference or referenceNumber. No suffix. A full mbreciept.cbe.com.et link is accepted. Older FT references still need an 8-digit suffix.
boaRequiredUse referenceNumber/reference plus accountSuffix/suffix exactly 5 digits.
telebirrRequiredUse transactionNumber or reference.
mpesaRequiredUse transactionNumber or reference. Receipt URLs and supported SMS text can be parsed.
cbebirrRequiredUse receiptNumber/reference plus phone/phoneNumber.
dashen / awash / siinqeeRequiredUse referenceNumber or reference.
kaafiebirrRequiredUse referenceNumber/reference. phone/phoneNumber is optional.
coopayebirrRequiredUse referenceNumber/reference with the SMS receipt link or its token (not the SMS Ref). phone/phoneNumber is optional.
CBE logoCBESend the Receipt ID in reference. No suffix. referenceNumber is the same field.
curl -X POST \
  "https://verify.et/api/verify?waitMs=5000" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  -d '{
    "bank": "cbe",
    "reference": "fHCxyExampleRcpt1"
  }'
Bank of Abyssinia logoBank of AbyssiniaRequires referenceNumber and a 5-digit accountSuffix.
curl -X POST \
  "https://verify.et/api/verify?waitMs=5000" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  -d '{
    "bank": "boa",
    "referenceNumber": "BOA123456789",
    "accountSuffix": "12345"
  }'
Telebirr logoTelebirrRequires transactionNumber or reference.
curl -X POST \
  "https://verify.et/api/verify?waitMs=5000" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  -d '{
    "bank": "telebirr",
    "transactionNumber": "TELE123456"
  }'
MPESA logoMPESAAccepts a transaction number, reference, supported receipt URL, or supported SMS text.
curl -X POST \
  "https://verify.et/api/verify?waitMs=5000" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  -d '{
    "bank": "mpesa",
    "transactionNumber": "MPESA123456"
  }'
CBE Birr logoCBE BirrRequires receiptNumber plus phone or phoneNumber.
curl -X POST \
  "https://verify.et/api/verify?waitMs=5000" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  -d '{
    "bank": "cbebirr",
    "receiptNumber": "CBE-BIRR-123456",
    "phone": "0911223344"
  }'
Dashen logoDashenRequires referenceNumber or reference.
curl -X POST \
  "https://verify.et/api/verify?waitMs=5000" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  -d '{
    "bank": "dashen",
    "referenceNumber": "DASHEN123456"
  }'
Awash logoAwashRequires referenceNumber or reference.
curl -X POST \
  "https://verify.et/api/verify?waitMs=5000" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  -d '{
    "bank": "awash",
    "referenceNumber": "AWASH123456"
  }'
Siinqee logoSiinqeeRequires referenceNumber or reference.
curl -X POST \
  "https://verify.et/api/verify?waitMs=5000" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  -d '{
    "bank": "siinqee",
    "referenceNumber": "SIINQEE123456"
  }'
Kaafi Ebirr logoKaafi EbirrRequires referenceNumber or reference.phone is optional.
curl -X POST \
  "https://verify.et/api/verify?waitMs=5000" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  -d '{
    "bank": "kaafiebirr",
    "referenceNumber": "https://receipt.ebirr.com/kaafimf/ilgi8ctn1Tr6vhw_so79kw"
  }'
Coopay Ebirr logoCoopay EbirrRequires referenceNumber or reference set to the receipt link from the SMS, or its case-sensitive token. The SMS Ref number alone cannot be verified. phone is optional.
curl -X POST \
  "https://verify.et/api/verify?waitMs=5000" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  -d '{
    "bank": "coopayebirr",
    "referenceNumber": "https://receipt.ebirr.com/coopay/T3stCoopayTok3n_AbCd1Q"
  }'

Current support note

zemen appears in status and bank enums, but direct POST /api/verify submissions for Zemen are not supported yet.

Verify.ET Agent Skill is here

Add the skill, then put your API key in .env. The agent follows this guide: register a webhook, save the secret, and submit verifications without a callback URL.

Install
npx skills add NegusNati/verify-et-api

Works with

CodexClaude CodeCursorGeminiOpenClawHermesAny Agent Skills compatible agent

Next up

API Reference

The API reference includes the webhook payload, signature headers, and management routes. The REST examples page has copyable receivers for Node.js, Python, and PHP.

View REST examples by language