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.
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.
Get Auth Token
Your server requests a Fabric Token from the TeleBirr gateway using your appSecret. Token is valid for 2 hours.
Create Order
Send order details (amount, title, merchant code) to get a prepay_id that identifies this payment session.
Launch SDK
Pass the prepay_id to the mobile SDK. TeleBirr app opens, the user enters their PIN and confirms payment.
Get Notified
TeleBirr sends a signed POST callback to your server with the payment result. Verify the signature!
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.
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private-key.pemopenssl pkey -in private-key.pem -out public-key.pem -puboutAPI 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.
Sandbox:
https://developerportal.ethiotelebirr.et:38443/apiaccess/payment/gatewayProduction:
https://superapp.ethiomobilemoney.et:38443/apiaccess/payment/gatewayRequest Body
appSecret requiredResponse
tokeneffectiveTime7200 = 2 hours).// Request POST /payment/v1/token Content-Type: application/json { "appSecret": "YOUR_FABRIC_APP_SECRET" } // Response (200 OK) { "effectiveTime": 7200, "token": "eyJhbGciOiJSUzI1NiIs..." }
Headers
X-APP-Key requiredAuthorization requiredBody (biz_content)
appid requiredmerch_code requiredmerch_order_id requiredtitle requiredtotal_amount required"10.00")trans_currency required"ETB")timeout_express optional"120m")trade_type required"Cross-App" for the native in-app SDK flowbusiness_type required"BuyGoods" for the native SDK flownotify_url optional// 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" } }
payee_identifier/payee_identifier_type/payee_typeare 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.titlemust not contain a hyphen (-). TeleBirr's title validation regex rejects it silently in a generic error — "Wallet Top-Up" fails, "Wallet TopUp" works.
Client-Side (Mobile App)
Using the prepay_id from Step 2, construct a receiveCode and launch TeleBirr checkout.
% 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.
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.
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
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.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.
[[EthiopiaPayManager sharedManager]
startPayWithAppId:appId
shortCode:shortCode
receiveCode:receiveCode
returnAppScheme:ReturnApp];
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 completedtotal_amount"100.00")merch_order_id
// 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"
}
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.Query Parameters
appid requiredmerch_code requiredmerch_order_id required// 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" } }
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
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
.aaras 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.frameworkas local dependency - Add
telebirrcustomerApptoQueried 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.
Collect parameters
Gather all request parameters including those under biz_content. Exclude: null values, sign, sign_type, and biz_content itself.
Sort alphabetically
Sort remaining parameters by key name in ASCII ascending order (lexicographic). Parameter names are case-sensitive.
Build rawRequest string
Join as URL key-value pairs: key1=value1&key2=value2&.... Skip parameters with empty values.
Sign with your private key
Apply SHA256WithRSA (PSS fill mode) to the rawRequest string using your merchant private key.
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.
| Code | Status | Description | What 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 |