Telebirr
Telebirr is Ethiopia’s dominant mobile money platform, operated by Ethio Telecom. With over 40 million registered users, it’s the most widely used payment method in Ethiopia by volume.
Overview
Section titled “Overview”| Property | Value |
|---|---|
| Operator | Ethio Telecom |
| Type | Mobile Money |
| Country | Ethiopia |
| Currency | ETB |
| Flow | USSD Push |
| Programmatic Refunds | No |
| Sandbox Available | Yes (limited) |
| Settlement | T+1 business days |
How Telebirr Payments Work
Section titled “How Telebirr Payments Work”Telebirr does not use a web checkout page. Instead, it initiates a USSD push to the customer’s phone. The flow is:
- Your app calls
zirzir.charge()with the customer’s phone number - Telebirr sends a USSD prompt to the customer’s phone
- Customer approves by entering their Telebirr PIN
- Telebirr sends a webhook to your
callbackUrl - You verify and fulfill the order
This means checkoutUrl will always be null for Telebirr transactions.
Credentials
Section titled “Credentials”You’ll need the following from Ethio Telecom’s merchant portal:
| Credential | Description |
|---|---|
appId | Your merchant App ID |
appKey | Your App Key (keep secret) |
shortCode | Your merchant short code (e.g., 950702) |
publicKey | Telebirr’s RSA public key (for encrypting auth payload) |
Getting credentials: Contact Ethio Telecom’s merchant services at their business centers. As of 2024, the process takes 2-4 weeks and requires a business license.
Configuration
Section titled “Configuration”const zirzir = new Zirzir({ provider: 'telebirr', credentials: { appId: process.env.TELEBIRR_APP_ID!, appKey: process.env.TELEBIRR_APP_KEY!, shortCode: process.env.TELEBIRR_SHORT_CODE!, publicKey: process.env.TELEBIRR_PUBLIC_KEY!, // RSA public key PEM }})client = zirzir.Zirzir( provider="telebirr", credentials={ "app_id": os.environ["TELEBIRR_APP_ID"], "app_key": os.environ["TELEBIRR_APP_KEY"], "short_code": os.environ["TELEBIRR_SHORT_CODE"], "public_key": os.environ["TELEBIRR_PUBLIC_KEY"], })Charging a Customer
Section titled “Charging a Customer”phone is required for Telebirr. It must be an Ethio Telecom number.
const tx = await zirzir.charge({ amount: 500, currency: 'ETB', phone: '0911234567', // Ethio Telecom number txRef: 'order_001', callbackUrl: 'https://yourapp.com/webhooks/zirzir',})
// tx.checkoutUrl is null — customer is prompted via USSDconsole.log(tx.status) // 'pending'Phone Number Formats
Section titled “Phone Number Formats”Zirzir normalizes phone numbers automatically, but we recommend passing them in 09XXXXXXXX or 2519XXXXXXXX format.
| Format | Accepted |
|---|---|
0911234567 | Yes |
+251911234567 | Yes |
251911234567 | Yes |
911234567 | Yes (normalized internally) |
Waiting for Payment
Section titled “Waiting for Payment”Since there’s no checkout page, you need to wait for the customer to approve. Two approaches:
Option A: Webhook (Recommended)
Section titled “Option A: Webhook (Recommended)”app.post('/webhooks/zirzir', express.raw({ type: 'application/json' }), (req, res) => { const valid = zirzir.webhooks.verify(req.body, req.headers['x-zirzir-signature']) if (!valid) return res.sendStatus(401)
const event = JSON.parse(req.body.toString())
if (event.type === 'transaction.success') { fulfillOrder(event.data.txRef) }
res.sendStatus(200)})Option B: Show a Waiting Screen + Poll
Section titled “Option B: Show a Waiting Screen + Poll”// After charge(), show "Check your phone" UI and poll every 5sconst tx = await pollUntilComplete('order_001', { maxAttempts: 24, interval: 5000 })Known Issues & Quirks
Section titled “Known Issues & Quirks”Sandbox Doesn’t Send USSD
Section titled “Sandbox Doesn’t Send USSD”The Telebirr sandbox environment does not actually send a USSD message to the phone. To test, you must call verify() manually after a short delay — sandbox transactions auto-succeed after ~30 seconds.
// In test mode, manually verify after a delayif (process.env.NODE_ENV !== 'production') { await sleep(30000) const tx = await zirzir.verify('order_001') console.log(tx.status) // 'success' in sandbox}Duplicate Transactions
Section titled “Duplicate Transactions”Telebirr’s API does not always reject duplicate txRef values in sandbox. In production it does. Always use unique references.
Auth Token Expiry
Section titled “Auth Token Expiry”Telebirr access tokens expire every 30 minutes. The Zirzir SDK handles token refresh automatically — you don’t need to manage this.
RSA Encryption
Section titled “RSA Encryption”Telebirr requires certain request parameters to be RSA-encrypted using their public key. The Zirzir SDK handles this internally, but you must provide the correct publicKey credential. If you get auth errors, double-check the PEM format of your key.
Timeout Behavior
Section titled “Timeout Behavior”If the customer doesn’t respond to the USSD prompt within 5 minutes, Telebirr marks the transaction as expired. Your webhook will receive a transaction.failed event with reason: 'timeout'.
Refunds
Section titled “Refunds”Telebirr does not support programmatic refunds. You must process refunds manually through the Ethio Telecom merchant portal or by contacting your Telebirr account manager.
Keep your transaction externalId values — you’ll need them for manual refund requests.
Fee Structure
Section titled “Fee Structure”Fee structures change. Verify current rates with your Ethio Telecom merchant agreement.
| Transaction Type | Fee |
|---|---|
| Merchant-initiated (standard) | 0.8% of transaction amount |
| Minimum fee | 1 ETB |
| Maximum fee | 100 ETB |
Settlement is net of fees — you receive the transaction amount minus the Telebirr fee.
Settlement
Section titled “Settlement”- Settlement cycle: T+1 business days (next business day)
- Minimum settlement amount: 100 ETB
- Settlement method: Transfer to linked bank account
- Settlement reports: Available in the Ethio Telecom merchant portal and in the Zirzir Dashboard (server mode)
Support & Escalation
Section titled “Support & Escalation”- Merchant support: Contact your Ethio Telecom account manager
- Sandbox access: Request via the Ethio Telecom developer portal
- Common issue: Transactions stuck in
pending— check yourcallbackUrlis publicly accessible and verify manually