Skip to main content

Getting Started

Integrate Ethiopian Transaction Verification

Create an API key, submit a verification, then choose whether your app waits briefly, polls for completion, streams status with SSE, or receives the result through a webhook.

API keysPollingSSEWebhooksBank-specific fields

Choose an Integration Flow

Short Wait

Submit with waitMs when you want a best-effort immediate answer and can handle a queued fallback.

Jump to Section
Polling or SSE

Use the returned status URL when your system owns the user-facing waiting experience.

Jump to Section
Webhooks

Send a callback URL when you want Verify.ET to push the terminal result to your backend.

Jump to Section

Create an API Key

  1. 1

    Sign in

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

    Create a key

    Open the dashboard API keys area, create a key, and save the full VERIFY_BANK_ET_your_key_here secret immediately. It is shown once.
  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.

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.

Verify.ET Agent Skill is here

Let your AI agent integrate Verify.ET and ship to production automatically. No docs. No manual setup. Just add your API key in your .env and let the agent handle the rest.

Install
npx skills add NegusNati/verify-et-api

Works with

CodexClaude CodeCursorGeminiOpenClawHermesAny Agent Skills compatible agent

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. 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.

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",
    "referenceNumber": "FT1234567890",
    "accountSuffix": "12345678"
  }'

Handle both 200 and 202

A 200 response includes the completed verification. A 202 response means the request is queued and includes a status URL you can poll or stream.
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
  }
}

Poll or Stream Status

Poll GET /api/verify/:requestId until processingStatus is completed or failed. For near real-time updates, open the SSE endpoint.

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)}`;

for (let attempt = 0; attempt < 20; attempt += 1) {
  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 before polling timeout.");
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.

Receive Results With Webhooks

Pass webhookUrl in the JSON body or X-Webhook-Url as a header. When the verification finishes, the worker sends verification.completed to your endpoint.

curl -X POST \
  "https://verify.et/api/verify" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $VERIFY_ET_API_KEY" \
  -H "X-Webhook-Url: https://your-app.com/webhooks/verify" \
  -d '{
    "bank": "telebirr",
    "transactionNumber": "TELE123456"
  }'

URL Validation

Production URLs must use HTTPS, must not include credentials, and must resolve to public addresses.

Delivery Headers

Expect X-Webhook-Event, X-Webhook-Timestamp, X-Webhook-Delivery-Id, and optional X-Webhook-Signature.

Retries

Default delivery policy retries up to 5 attempts: immediate, 30s, 2m, 8m, then 30m.

Signature verification

If a signing secret is configured, compute HMAC-SHA256 over ${X-Webhook-Timestamp}.${rawJsonBody} and compare it with X-Webhook-Signature in the format sha256=<hex>.

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. Can map to a bank reference, transaction number, receipt number, URL, or token.
suffixOptionalUniversal account suffix. Use 8 digits for CBE or 5 digits for Bank of Abyssinia.
phoneNumberOptionalUniversal CBE Birr phone. Accepts 09XXXXXXXX or 251XXXXXXXXX and normalizes to 251XXXXXXXXX.
webhookUrlOptionalCallback URL for result delivery. In production it must be public HTTPS and must not resolve to private or reserved IP space.

Explicit Bank Payloads

FieldRequiredDescription
cbeRequiredUse referenceNumber/reference plus accountSuffix/suffix exactly 8 digits.
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.
CBE logoCBERequires referenceNumber and an 8-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": "cbe",
    "referenceNumber": "FT1234567890",
    "accountSuffix": "12345678"
  }'
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"
  }'

Current support note

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

Next up

API Reference

The full reference includes exact response envelopes, endpoint permissions, rate-limit behavior, and copyable request examples for each supported integration path.

View REST examples by language