# EthioVerify Payment Verification API

EthioVerify verifies Ethiopian payment receipts or payment references against supported bank sources.

Base URL:

```text
https://paymentverify.tibebclass.com/api
```

Use Direct Call when your backend needs the bank result in the HTTP response. Use Webhook Verify when your backend wants EthioVerify to accept the request quickly, verify in the background, and notify your webhook with the final result.

## 1. Merchant Setup

1. Create an EthioVerify account and log in.
2. Open Gateway and verify your account email.
3. After email verification, go to API Keys.
4. Create an API key.
5. Add a webhook URL if you use Webhook Verify or want retry/manual-review callbacks.
6. Set a webhook verification key or leave it empty so EthioVerify generates one.
7. Store the API key and webhook verification key on your backend only.

API key format:

```text
ev_...
```

Webhook verification key format:

```text
whsec_...
```

## 2. Verification Modes

### Option A: Direct Call

Direct Call verifies immediately and returns the final result in the HTTP response.

Endpoint:

```http
POST https://paymentverify.tibebclass.com/api/gateway/verify-direct/
X-EthioVerify-Key: ev_your_api_key_here
Content-Type: multipart/form-data
```

Example fields:

```text
amount=500.00
bank=telebirr
reference=DE07AVZM5T
receiver_name=ORGANO SHAKE JUICE 4KILO BRANCH
merchant_order_id=ORDER-1001
receipt=@receipt.jpg
```

Direct Call does not call your webhook for the initial result. Use the HTTP response directly.

Accepted aliases:

- API key header: `X-EthioVerify-Key`, `X-API-Key`, or `Authorization: Bearer ev_...`
- Amount: `amount` or `expected_amount`
- Reference: `reference` or `payment_number`
- Receipt file: `receipt` or `receipt_image`
- Receiver name: `receiver_name` or `account_holder_name`
- Merchant order id: `merchant_order_id` or `order_id`
- Receiver account digits: `account_suffix` or `account_number`
- Receipt link: `qr_link` or `receipt_link`

### Option B: Webhook Verify

Webhook Verify stores the request as pending, returns quickly, verifies in the background, then calls your webhook with the final result.

Endpoint:

```http
POST https://paymentverify.tibebclass.com/api/gateway/verify-async/
X-EthioVerify-Key: ev_your_api_key_here
Content-Type: multipart/form-data
```

Example fields:

```text
amount=500.00
bank=telebirr
reference=DE07AVZM5T
receiver_name=ORGANO SHAKE JUICE 4KILO BRANCH
merchant_order_id=ORDER-1001
receipt=@receipt.jpg
```

Initial response:

```json
{
  "success": true,
  "status": "pending",
  "request_id": 42,
  "merchant_order_id": "ORDER-1001",
  "message": "Verification request accepted. EthioVerify will notify your webhook with the final result."
}
```

Webhook URL is required for this mode. You can configure it on the API key or send `webhook_url` in the request.

Accepted aliases are the same as Direct Call. Webhook Verify also accepts `failure_email` for failed verification notifications and `webhook_url` when you want to override the API key webhook URL for that request.

## 3. Request Fields

| Field | Required | Description |
| --- | --- | --- |
| `amount` / `expected_amount` | Yes | Expected amount. Receipt amount must be equal or greater. |
| `receiver_name` / `account_holder_name` | Yes | Expected receiver or account holder name. |
| `reference` / `payment_number` | Conditional | Payment reference or transaction number. Required if no receipt file or receipt link is sent. |
| `receipt` / `receipt_image` | Conditional | Receipt image or PDF. Required if no reference or receipt link is sent. |
| `bank` | Optional | Supported bank code. EthioVerify can detect it for many references. |
| `merchant_order_id` / `order_id` | Recommended | Your internal order/payment id. Returned in responses and webhooks. |
| `account_suffix` / `account_number` | Bank-specific | Receiver account digits for CBE and Bank of Abyssinia. Full account number is accepted. |
| `phone_number` | Bank-specific | Required for phone-based verifiers such as CBE Birr. |
| `webhook_url` | Async-specific | Optional if already configured on the API key. Required for Webhook Verify overall. |
| `failure_email` | Optional | Email for failed verification notifications. Defaults to the API key failure email or account email. |
| `qr_link` / `receipt_link` | Optional | Receipt URL from a QR code. Normally EthioVerify extracts this from uploaded receipts automatically. |

Receipt files may be image formats or PDF.

## 4. Bank-Specific Inputs

| Bank | Code | Required Fields | Rule |
| --- | --- | --- | --- |
| Telebirr | `telebirr` | `reference` or `receipt`, `amount`, `receiver_name` | No account suffix or phone number required. |
| Commercial Bank of Ethiopia | `cbe` | `reference` or `receipt`, `amount`, `receiver_name`, `account_suffix` | Send full receiver account number or last 8 digits. EthioVerify keeps the last 8 digits. CBE QR receipt links can be verified without account digits. |
| Bank of Abyssinia | `abyssinia` | `reference` or `receipt`, `amount`, `receiver_name`, `account_suffix` | Send full receiver account number or last 5 digits. EthioVerify keeps the last 5 digits. |
| CBE Birr | `cbe_birr` | `reference` or `receipt`, `amount`, `receiver_name`, `phone_number` | Ethiopian mobile number: `2519XXXXXXXX`, `2517XXXXXXXX`, `09XXXXXXXX`, or `07XXXXXXXX`. |
| Dashen | `dashen` | `reference` or `receipt`, `amount`, `receiver_name` | No account suffix or phone number required. |
| M-Pesa | `mpesa` | `reference` or `receipt`, `amount`, `receiver_name` | No account suffix or phone number required for the current verifier. |

For CBE and Abyssinia, `account_suffix` means receiver account digits, not sender account digits. You can send the full receiver account number as `account_number`; EthioVerify uses only the required ending digits.

## 5. Direct Call Responses

Verified response:

```json
{
  "success": true,
  "status": "verified",
  "merchant_order_id": "ORDER-1001",
  "bank": "telebirr",
  "reference": "DE07AVZM5T",
  "expected_amount": "500.00",
  "paid_amount": "500.00",
  "receiver_name": "ORGANO SHAKE JUICE 4KILO BRANCH",
  "payer_name": "CUSTOMER NAME",
  "message": "Payment verified.",
  "transaction_date": "2026-05-31T10:30:29+03:00"
}
```

Failed response:

```json
{
  "success": false,
  "status": "receiver_mismatch",
  "merchant_order_id": "ORDER-1001",
  "reference": "DE07AVZM5T",
  "message": "Receipt receiver name does not match the submitted receiver account holder name.",
  "error": "Receipt receiver name does not match the submitted receiver account holder name."
}
```

## 6. Webhook Callback

EthioVerify calls your webhook when:

- an async verification reaches a final state
- a retry succeeds
- a request is manually approved
- a request is manually declined

Webhook events:

```text
payment.verified
payment.failed
payment.failed_after_manual_check
```

Verified callback:

```json
{
  "event": "payment.verified",
  "merchant_request_id": "ORDER-1001",
  "merchant_order_id": "ORDER-1001",
  "status": "verified",
  "success": true,
  "bank": "telebirr",
  "reference": "DE07AVZM5T",
  "expected_amount": "500.00",
  "paid_amount": "500.00",
  "receiver_name": "ORGANO SHAKE JUICE 4KILO BRANCH",
  "payer_name": "CUSTOMER NAME",
  "message": "Payment verified."
}
```

Failed callback:

```json
{
  "event": "payment.failed",
  "merchant_order_id": "ORDER-1001",
  "status": "receiver_mismatch",
  "success": false,
  "reference": "DE07AVZM5T",
  "message": "Receipt receiver name does not match the submitted receiver account holder name."
}
```

Manual failure callback:

```json
{
  "event": "payment.failed_after_manual_check",
  "merchant_order_id": "ORDER-1001",
  "status": "manual_failed",
  "success": false,
  "reference": "DE07AVZM5T",
  "message": "Payment failed after manual review."
}
```

Webhook headers:

```http
X-EthioVerify-Event: payment.verified
X-EthioVerify-Signature: hmac_sha256_signature
```

The signature is an HMAC-SHA256 hash of the canonical JSON webhook body using the webhook verification key saved on the API key.

Python/Django verification:

```python
import hashlib
import hmac
import json

def verify_ethioverify_webhook(request, webhook_secret):
    received_signature = request.headers.get("X-EthioVerify-Signature", "")
    payload = json.loads(request.body.decode("utf-8"))
    canonical_payload = json.dumps(payload, sort_keys=True, separators=(",", ":"))
    expected_signature = hmac.new(
        webhook_secret.encode("utf-8"),
        canonical_payload.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(received_signature, expected_signature)
```

Node.js verification:

```js
const crypto = require('crypto')

function stableStringify(value) {
  if (Array.isArray(value)) {
    return '[' + value.map(stableStringify).join(',') + ']'
  }
  if (value && typeof value === 'object') {
    return '{' + Object.keys(value).sort().map((key) =>
      JSON.stringify(key) + ':' + stableStringify(value[key])
    ).join(',') + '}'
  }
  return JSON.stringify(value)
}

function verifyEthioVerifyWebhook(req, webhookSecret) {
  const signature = req.headers['x-ethioverify-signature']
  const payload = stableStringify(req.body)
  const expected = crypto
    .createHmac('sha256', webhookSecret)
    .update(payload)
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(signature || '', 'hex'),
    Buffer.from(expected, 'hex')
  )
}
```

## 7. Statuses

| Status | Meaning |
| --- | --- |
| `pending` | Request accepted and waiting for background verification. |
| `verified` | Payment is valid. |
| `failed` | Verification failed automatically. |
| `manual_failed` | A human reviewed and rejected the payment. |
| `duplicate` | The same verified receipt was already used for this merchant account. |
| `amount_mismatch` | Receipt amount is lower than expected. |
| `receiver_mismatch` | Receipt receiver does not match the submitted receiver name. |
| `error` | Bank source, network, OCR, or relay failed. |

## 8. Security Rules

- Call EthioVerify from your backend, never directly from customer browsers.
- Keep API keys and webhook verification keys secret.
- Verify `X-EthioVerify-Signature` before trusting a webhook.
- Use HTTPS webhook URLs in production.
- Always check `success`, `status`, `paid_amount`, `receiver_name`, and your own `merchant_order_id` before marking an order paid.
- Treat `paid_amount` greater than expected as acceptable only if your business policy allows overpayment.
