H5 C2B Web Checkout
Redirect your web customers to the Telebirr-hosted payment page. They enter their phone and PIN, get a confirmation, and your server receives a signed notification.
Using PHP or JS/TS? The melaku/telebirr (PHP) and @melakudemeke/telebirr-js (JS/TS) libraries do all four steps below in one call. Skip to quickstart →
How it works
Your server Telebirr Customer
────────────────────────────────────────────────────────────────────
① POST /payment/v1/token → Get bearer token
② POST /merchant/preOrder → Get prepay_id
③ Build signed checkout URL
④ Redirect customer → ──────────────────────→ Pays
⑤ ← POST to notifyUrl (async)
⑥ Customer redirected back ← ──────────────────────← DoneFour API calls, a URL redirect, and an async notification. That’s the whole flow.
Quickstart
composer require melaku/telebirruse Melaku\Telebirr\Config;
use Melaku\Telebirr\Telebirr;
$config = Config::forProduction([
'fabricAppId' => $_ENV['FABRIC_APP_ID'],
'appSecret' => $_ENV['APP_SECRET'],
'merchantAppId' => $_ENV['MERCHANT_APP_ID'],
'merchantCode' => $_ENV['MERCHANT_CODE'],
'privateKey' => $_ENV['PRIVATE_KEY_PEM'],
'notifyUrl' => 'https://your-site.com/pay/notify',
'redirectUrl' => 'https://your-site.com/pay/return',
]);
$result = (new Telebirr($config))->createCheckoutUrl('Order #1337', '350.00');
// ⚠️ Persist this BEFORE redirecting. It's how you match the notification.
saveOrder($result->getMerchOrderId(), $result->getPrepayId());
header('Location: ' . $result->getCheckoutUrl());The library handles ① ② ③ in that one call. You just do the redirect.
Paste your keys exactly as Ethio Telecom issues them. Since PHP v2.2.0 and JS/TS v3.1.0, the libraries accept bare base64 keys (no -----BEGIN…----- armor needed) and normalize them to PEM automatically. TLS against the test gateway also works out of the box now — the libraries handle its incomplete certificate chain, so you never need verifySsl: false.
Verify the payment
When the customer comes back (or the notification arrives), confirm the real status server-to-server with one call — never trust the browser redirect:
$status = $client->getOrderStatus($merchOrderId); // token handled internally
if ($status->paid && $status->amount === $expectedAmount) {
// fulfill idempotently — see the settlement pattern in the library README
}getOrderStatus() (PHP v2.2.0+, JS v3.1.0+) returns paid / failed / cancelled, the amount, currency, and Telebirr’s paymentOrderId — typed, with the raw response attached.
Step-by-step (all languages)
Get the bearer token that authorizes all API calls.
① Apply Fabric TokenRegister the transaction and receive a prepay_id.
Sign and assemble the redirect URL.
③ Checkout URLReceive and verify the payment callback.
④ NotificationsEnvironment URLs
| Base URL | Web Checkout URL | |
|---|---|---|
| Test | developerportal.ethiotelebirr.et:38443/apiaccess/payment/gateway | developerportal.ethiotelebirr.et:38443/payment/web/paygate? |
| Production | superapp.ethiomobilemoney.et:38443/apiaccess/payment/gateway | superapp.ethiomobilemoney.et:38443/payment/web/paygate? |
Mixing test and production URLs is a very common source of failures. The library’s Config::forTest() and Config::forProduction() set both URLs together so you can’t accidentally cross them.