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.
Choose an Integration Flow
Create an API Key
- 1
Sign in
Open/loginand authenticate with your account. - 2
Create a key
Create a key and save the fullVERIFY_BANK_ET_your_key_heresecret. It is shown once.Open API Keys - 3
Set permissions
For verification flows, includeverification:writeto submit requests andverification:readto poll, stream, or read history. - 4
Send the header
Addx-api-keyto every API request. AddIdempotency-Keyto safely retry submissions.
Next, add a webhook
Register one URL. Verify.et sends the finished result there.
Open WebhooksCredits are account-level
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
Add the endpoint
Open Webhooks, add your URL, and copy thewhsec_secret. It is shown once.Open Webhooks - 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
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?
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.
VERIFY_ET_BASE_URL=https://verify.et
VERIFY_ET_API_KEY=VERIFY_BANK_ET_your_key_hereSubmit 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
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.{
"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
OptionalSend a photo on the same POST /api/verify request. Put the file in one part named image.
- 1
Turn it on
Image Processing starts off. The billing owner turns on the switch.Enable Image Processing - 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
Send the photo
Post the file withx-api-keyandIdempotency-Key. Use JPEG, PNG, or WebP, up to 8 MB.200is finished and202is queued. ReadimageProcessingon 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"{
"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
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.
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.");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
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
| Field | Required | Description |
|---|---|---|
| bank | Optional | Optional for universal routing. Use it when you already know the provider and want strict validation. |
| reference | Optional | Universal input. For CBE, put the Receipt ID here. It can also be a bank reference, transaction number, receipt URL, or token. |
| suffix | Optional | Account 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. |
| phoneNumber | Optional | Universal CBE Birr phone. Accepts 09XXXXXXXX or 251XXXXXXXXX and normalizes to 251XXXXXXXXX. |
| webhookUrl | Optional | Optional one-off callback. Prefer an active dashboard webhook endpoint, then leave this field out. |
Explicit Bank Payloads
| Field | Required | Description |
|---|---|---|
| cbe | Required | Send 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. |
| boa | Required | Use referenceNumber/reference plus accountSuffix/suffix exactly 5 digits. |
| telebirr | Required | Use transactionNumber or reference. |
| mpesa | Required | Use transactionNumber or reference. Receipt URLs and supported SMS text can be parsed. |
| cbebirr | Required | Use receiptNumber/reference plus phone/phoneNumber. |
| dashen / awash / siinqee | Required | Use referenceNumber or reference. |
| kaafiebirr | Required | Use referenceNumber/reference. phone/phoneNumber is optional. |
| coopayebirr | Required | Use referenceNumber/reference with the SMS receipt link or its token (not the SMS Ref). phone/phoneNumber is optional. |
CBESend 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 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"
}'
TelebirrRequires 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"
}'
MPESAAccepts 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 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"
}'
DashenRequires 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"
}'
AwashRequires 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"
}'
SiinqeeRequires 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 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 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.
npx skills add NegusNati/verify-et-apiWorks with
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