TeleBirr Docs
InApp SDK Integration

Accept TeleBirr payments in your app.

Integrate Ethiopia's leading mobile money platform into your Android or iOS application. Let users pay directly from their TeleBirr wallet — fast, secure, and seamless.

Your AppInitiates payment
TeleBirr AppUser enters PIN & pays
Your BackendReceives payment notification
Order CompleteConfirmed & fulfilled

Service Flow

Five steps to accept payments

Your backend handles token & order creation. The mobile SDK opens TeleBirr checkout. You get notified when the user pays.

01

Get Auth Token

Your server requests a Fabric Token from the TeleBirr gateway using your appSecret. Token is valid for 2 hours.

02

Create Order

Send order details (amount, title, merchant code) to get a prepay_id that identifies this payment session.

03

Launch SDK

Pass the prepay_id to the mobile SDK. TeleBirr app opens, the user enters their PIN and confirms payment.

04

Get Notified

TeleBirr sends a signed POST callback to your server with the payment result. Verify the signature!

05

Query Status

Didn't get the callback? Use the queryOrder endpoint to check payment status anytime for reconciliation.

Before You Start

Your integration credentials

Register as a merchant on the Fabric portal (online or offline). You'll receive these credentials to start integrating.

Fabric App ID

Identifies your app on the Fabric platform. Used as X-APP-Key header.

App Secret

Used server-side to request auth tokens. Never expose in mobile code.

Merchant Short Code

Your unique short code registered with Mobile Money for receiving payments.

RSA Key Pair

Generate 2048-bit RSA keys. Send the public key to TeleBirr, keep the private key safe.

Generate your RSA keys:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private-key.pem
openssl pkey -in private-key.pem -out public-key.pem -pubout

API Reference

Complete endpoint documentation

All API calls are backend-to-backend (server-side) except Step 3 which runs on the mobile device via the SDK.

Base URLs
Sandbox: https://developerportal.ethiotelebirr.et:38443/apiaccess/payment/gateway
Production: https://superapp.ethiomobilemoney.et:38443/apiaccess/payment/gateway
POST /payment/v1/token STEP 1

Request Body

appSecret required
Your Fabric App Secret. Obtained during merchant registration.

Response

token
Authentication token for subsequent API calls.
effectiveTime
Token validity in seconds (default 7200 = 2 hours).
Request → Response
// Request
POST /payment/v1/token
Content-Type: application/json

{
  "appSecret": "YOUR_FABRIC_APP_SECRET"
}

// Response (200 OK)
{
  "effectiveTime": 7200,
  "token": "eyJhbGciOiJSUzI1NiIs..."
}
Rate Limit: A Fabric App ID can call the token endpoint only 100 times per day. Cache your token and reuse it — request a new one every 30 minutes at most.
POST /payment/v1/merchant/preOrder STEP 2

Headers

X-APP-Key required
Your Fabric App ID
Authorization required
Token from Step 1

Body (biz_content)

appid required
Fabric App ID
merch_code required
Merchant Short Code
merch_order_id required
Unique order ID from your system
title required
Order title / description
total_amount required
Payment amount (e.g. "10.00")
trans_currency required
Currency code ("ETB")
timeout_express optional
Order expiry (e.g. "120m")
trade_type required
Use "Cross-App" for the native in-app SDK flow
business_type required
Use "BuyGoods" for the native SDK flow
notify_url optional
Your webhook endpoint. Must be http(s) — see the notify gotcha below
Request → Response
// Request — native SDK / in-app flow
POST /payment/v1/merchant/preOrder
X-APP-Key: YOUR_FABRIC_APP_ID
Authorization: YOUR_TOKEN

{
  "appid": "1072905731584000",
  "merch_code": "200001",
  "merch_order_id": "ORD20240101001",
  "title": "Wallet TopUp",
  "total_amount": "100.00",
  "trans_currency": "ETB",
  "trade_type": "Cross-App",
  "business_type": "BuyGoods",
  "timeout_express": "120m",
  "notify_url": "https://your-server.com/api/telebirr/notify"
}

// Response (200 OK)
{
  "result": "SUCCESS",
  "code": "0",
  "msg": "success",
  "biz_content": {
    "prepay_id": "PRE_2024010100001"
  }
}
Two things that broke a real integration here:
  • payee_identifier / payee_identifier_type / payee_type are documented as optional, but sending them at all got us "identity information is illegal" on our merchant account. We only got a working order by omitting them entirely — try without first.
  • title must not contain a hyphen (-). TeleBirr's title validation regex rejects it silently in a generic error — "Wallet Top-Up" fails, "Wallet TopUp" works.
SDK PaymentManager.pay() / EthiopiaPayManager.startPay() STEP 3

Client-Side (Mobile App)

Using the prepay_id from Step 2, construct a receiveCode and launch TeleBirr checkout.

The vendor's published formula is wrong. It uses % before the timestamp, which produces only 5 $-separated parts. TeleBirr's own app parses receiveCode with split("\$") and requires exactly 6 — index 4 must equal prepay_id. With % in there, every payment fails with SDK error -2 Param Error, and nothing in the error message points at this. We only found the real cause by decompiling TeleBirr's own APK.
TELEBIRR$BUYGOODS${shortCode}${amount}${prepay_id}%{time}
TELEBIRR$BUYGOODS${shortCode}${amount}${prepay_id}${time}

amount is fixed to 2 decimals (Number(amount).toFixed(2)). time is yyyyMMddHHmmss — build it from UTC, not local time, or the code can fail validation depending on the server's timezone.

JavaScript — building receiveCode server-side
function buildReceiveCode({ amount, prepayId, shortCode }) {
  const time = formatDateTimeUTC(new Date());
  const formattedAmount = Number(amount).toFixed(2);
  return `TELEBIRR$BUYGOODS$${shortCode}$${formattedAmount}$${prepayId}$${time}`;
}

function formatDateTimeUTC(date) {
  const pad = (n) => String(n).padStart(2, "0");
  return (
    date.getUTCFullYear() +
    pad(date.getUTCMonth() + 1) +
    pad(date.getUTCDate()) +
    pad(date.getUTCHours()) +
    pad(date.getUTCMinutes()) +
    pad(date.getUTCSeconds())
  );
}

AndroidVerified against the real .aar

The API in TeleBirr's published integration guide (startPay(activity, appId, shortCode, receiveCode, callback) with separate onSuccess/onFailure methods) does not exist on the SDK build we received (EthiopiaPaySdkModule-uat-release.aar). We found the real API by running javap on the actual .aar's classes.jar.
Java — the actual working API
PaymentManager.getInstance().setPayCallback(new PayCallback() {
    @Override
    public void onPayCallback(int code, String msg) {
        // code 0 = success. See Error Codes below for the rest —
        // there is only ONE callback method, not onSuccess/onFailure.
    }
});

PayInfo payInfo = new PayInfo.Builder()
    .setAppId("YOUR_FABRIC_APP_ID")
    .setShortCode("YOUR_SHORT_CODE")
    .setReceiveCode("CONSTRUCTED_RECEIVE_CODE")
    .build();

PaymentManager.getInstance().pay(fragmentActivity, payInfo);

iOSUnverified — vendor docs only

Our production integration is Android-only. This snippet is Ethio Telecom's published API and has not been verified against a real .framework the way the Android SDK above was — given how wrong the Android docs turned out to be, treat this as a starting point, not a guarantee.

Objective-C
[[EthiopiaPayManager sharedManager]
    startPayWithAppId:appId
            shortCode:shortCode
          receiveCode:receiveCode
     returnAppScheme:ReturnApp];
POST your-server.com/api/telebirr/notify STEP 4

Webhook Callback

TeleBirr sends a POST to your server with payment results. The payload is signed — always verify the signature using TeleBirr's public key before processing.

Callback Payload (biz_content)

trade_status
"PAY_SUCCESS" when payment completed
total_amount
Paid amount (e.g. "100.00")
merch_order_id
Your original order ID
Callback Payload
// TeleBirr → Your Server
POST /api/telebirr/notify

{
  "biz_content": {
    "trade_status": "PAY_SUCCESS",
    "total_amount": "100.00",
    "merch_order_id": "ORD-20240101-001",
    "trade_no": "TXN_98765432"
  },
  "sign": "BASE64_RSA_SIGNATURE",
  "sign_type": "SHA256WithRSA"
}
Security: Always verify the callback signature using TeleBirr's public key before updating your order status. Never trust unsigned notifications.
Don't rely on this webhook alone. In our production deployment it silently never fired for several real, successfully-paid transactions — no error, no retry, they just sat at "pending" forever. The likely cause is a notify_url that wasn't reachable from TeleBirr's servers at the moment they tried to call it (DNS not yet propagated, container not yet healthy, etc.), and there's no visibility into that failure from your side. Always pair this with active queryOrder polling (Step 5) as the source of truth, and make your settlement function idempotent — check the transaction is still pending before crediting anything, since both the webhook and your polling can resolve the same order.
POST /payment/v1/merchant/queryOrder STEP 5

Query Parameters

appid required
Fabric App ID
merch_code required
Merchant Short Code
merch_order_id required
Your order ID to look up
Example
// Request — method must be lowercase
POST /payment/v1/merchant/queryOrder
X-APP-Key: YOUR_FABRIC_APP_ID
Authorization: YOUR_TOKEN

{
  "appid": "1072905731584000",
  "merch_code": "200001",
  "merch_order_id": "ORD20240101001",
  "method": "payment.queryorder"
}

// Response
{
  "biz_content": {
    "order_status": "PAY_SUCCESS",
    "total_amount": "100.00"
  }
}
Case sensitivity: the method value is payment.queryorder — all lowercase, no camelCase. We only discovered this from the wording of a live 400 response; it isn't called out anywhere in the published docs.

Possible Order Statuses

PAY_SUCCESS
PAY_FAILED
WAIT_PAY
PAYING
ACCEPTED
ORDER_CLOSED
REFUNDING
REFUND_SUCCESS
REFUND_FAILED

Platform SDKs

Android & iOS setup guides

Import the SDK library, configure your project, and you're ready to accept payments.

Android SDK Verified

EthiopiaPaySdkModule-uat-release.aar

Setup Steps

  • Import .aar as a local module dependency
  • Sync Gradle and add module to your build.gradle
  • Add ProGuard rules for the SDK
  • Call PaymentManager.getInstance().setPayCallback() then .pay() — see Step 3

ProGuard Rules

-keep class com.huawei.ethiopia.pay.sdk.api.core.** { *; }

iOS SDK Unverified

EthiopiaPaySDK.framework

Setup Steps

  • Add EthiopiaPaySDK.framework as local dependency
  • Add telebirrcustomerApp to Queried URL Schemes
  • Configure custom return URL Scheme in Info.plist
  • Forward callbacks in AppDelegate

AppDelegate Setup

- (BOOL)application:(UIApplication *)app
         openURL:(NSURL *)url
         options:(NSDictionary *)options {
    [[EthiopiaPayManager sharedManager]
        handleOpenURL:url];
    return YES;
}

Security

Request signing explained

Every API request must be signed with SHA256WithRSA (PSS padding). Here's how to construct the signature.

1

Collect parameters

Gather all request parameters including those under biz_content. Exclude: null values, sign, sign_type, and biz_content itself.

2

Sort alphabetically

Sort remaining parameters by key name in ASCII ascending order (lexicographic). Parameter names are case-sensitive.

3

Build rawRequest string

Join as URL key-value pairs: key1=value1&key2=value2&.... Skip parameters with empty values.

4

Sign with your private key

Apply SHA256WithRSA (PSS fill mode) to the rawRequest string using your merchant private key.

Example rawRequest (Create Order)
appid=1072905731584000&business_type=BuyGoods&merch_code=200001
&merch_order_id=201907161732001&method=payment.preorder
&nonce_str=fcab0d2949e64a69a212aa83eab6ee1d
¬ify_url=http://test.payment.com/notify
&redirect_url=http://test.payment.com/redirect
&timeout_express=120m×tamp=1535166225
&title=iphone1&total_amount=12&trade_type=Checkout
&trans_currency=ETB&version=1.0

From a real production build

Field notes — what the official docs get wrong

Everything below was found by actually shipping a TeleBirr integration: reverse-engineering the real SDK with javap, decompiling TeleBirr's own APK with dexdump to find the real receiveCode parser, and reconciling live sandbox traffic. None of it is called out in the published documentation. Paste this whole section into your own docs, or hand the downloadable guide to an AI assistant when integrating.

The receiveCode formula has a typo that breaks every payment

The published formula joins the timestamp with %. TeleBirr's own app requires exactly 6 $-separated parts and rejects anything else with an unhelpful -2 Param Error. Use $ before the timestamp, not %. See Step 3.

The documented Android API doesn't exist on the real SDK

startPay(activity, appId, shortCode, receiveCode, callback) with onSuccess/onFailure is not present in EthiopiaPaySdkModule-uat-release.aar. The real entry point is PaymentManager.getInstance().setPayCallback(...) then .pay(activity, PayInfo), with a single onPayCallback(int code, String msg). Verify against your own .aar with javap before trusting any code sample — including this one.

Optional identity fields can get your order rejected

payee_identifier, payee_identifier_type, and payee_type are documented as optional on preOrder. On our merchant account, sending any of them produced "identity information is illegal". Omit them first; only add them back if your account specifically requires them.

The notify webhook is not guaranteed to arrive

It failed silently for real, successfully-paid orders in production — no retry, no error, nothing to catch. Treat queryOrder polling as your source of truth and the webhook as a latency optimization on top of it. Make settlement idempotent: check the order is still pending before crediting anything, since both paths can resolve the same order.

Order titles can't contain a hyphen

"Wallet Top-Up" is rejected by title validation; "Wallet TopUp" passes. The failure mode doesn't name the field, so this is easy to lose an hour to.

The sandbox gateway's TLS certificate won't verify normally

Standard certificate verification fails against the sandbox host. We isolated this to a dedicated HTTP client used only for TeleBirr calls with relaxed verification — never disable certificate verification globally for your whole app.

redirect_url is for the browser checkout flow only

The native SDK flow doesn't need redirect_url at all — omit it. If you do use it (H5/WebCheckout), it must be a real http(s) URL; a custom app scheme (yourapp://) is rejected outright with a type-mismatch error.

Troubleshooting

SDK error codes

These are the error codes returned by the mobile SDK PayCallback. Handle each case in your app's UI. On Android they all arrive through the single onPayCallback(int code, String msg) method — see Step 3.

CodeStatusDescriptionWhat to do
0 Success Payment completed successfully Show success screen, await backend confirmation
-1 Unknown An unknown error occurred Log details, show generic error, allow retry
-2 Param Error Invalid parameters passed to the SDK Check appId, shortCode, receiveCode format
-3 Cancelled User cancelled the payment Return to cart/checkout, no action needed
-10 Not Installed TeleBirr app is not installed on device Prompt user to install TeleBirr from store
-11 Unsupported TeleBirr version doesn't support this feature Ask user to update TeleBirr to latest version