H5 C2B IntegrationRequest Signature

Request Signature

Every request to the Telebirr API must be signed with your RSA private key. The server verifies your signature using the public key you registered. Get this wrong and you get "verify sign failed" with no further explanation.

PHP library users: signing is done transparently. You never call the signer directly. This page is the reference for everyone writing raw implementations or debugging a signature mismatch.


How the signature works

Collect all request fields

Flatten biz_content sub-fields into the top-level set. Include everything except sign and sign_type (they don’t exist yet). Skip null and empty-string values — they’re excluded from the signature.

Sort by ASCII key order

Alphabetical order of the key names, case-sensitive. A comes before a in ASCII.

Join as key=value&key=value

appid=930231098009602&business_type=BuyGoods&callback_info=From web&merch_code=101011...

Sign with SHA256WithRSA + PSS padding

Feed the joined string into a SHA256 digest, sign with your RSA private key using PSS fill mode (RSASSA-PSS). Base64-encode the result.

Add to the request

Set sign to the base64 string and sign_type to "SHA256WithRSA".


Implementation

function signRequestObject(array $req, string $privateKeyPem): string {
    // 1. Flatten biz_content into top-level
    $flat = [];
    foreach ($req as $k => $v) {
        if ($k === 'sign' || $k === 'sign_type') continue;
        if ($v === null || $v === '') continue;
 
        if ($k === 'biz_content' && is_array($v)) {
            foreach ($v as $bk => $bv) {
                if ($bv !== null && $bv !== '') $flat[$bk] = (string) $bv;
            }
        } else {
            $flat[$k] = (string) $v;
        }
    }
 
    // 2. Sort by ASCII key order
    ksort($flat);
 
    // 3. Join as key=value&...
    $rawStr = http_build_query($flat);
 
    // 4. Sign with SHA256WithRSA PSS
    $privateKey = openssl_pkey_get_private($privateKeyPem);
    openssl_sign($rawStr, $signature, $privateKey, OPENSSL_ALGO_SHA256);
    // Note: openssl_sign with OPENSSL_ALGO_SHA256 uses PSS by default in most builds.
    // If you get verify failures, confirm your OpenSSL version supports PSS.
 
    return base64_encode($signature);
}

Example

Given this request body:

{
  "timestamp": "1684481139",
  "nonce_str":  "XG9C5S6R0NLEYF1AGYW5BT237SMDYCUH",
  "method": "payment.preorder",
  "version": "1.0",
  "biz_content": {
    "notify_url": "https://your-site.com/notify",
    "appid": "930231098961202",
    "merch_code": "123456",
    "merch_order_id": "1684481138534",
    "trade_type": "Checkout",
    "title": "diamond_1.5",
    "total_amount": "1.5",
    "trans_currency": "ETB",
    "timeout_express": "120m",
    "redirect_url": "https://your-site.com/return"
  }
}

The pre-signature string (all fields flattened + sorted):

appid=930231098961202&merch_code=123456&merch_order_id=1684481138534&method=payment.preorder&nonce_str=XG9C5S6R0NLEYF1AGYW5BT237SMDYCUH&notify_url=https://your-site.com/notify&redirect_url=https://your-site.com/return&timeout_express=120m&timestamp=1684481139&title=diamond_1.5&total_amount=1.5&trade_type=Checkout&trans_currency=ETB&version=1.0

Sign that string, base64-encode the result, put it in sign.


Common failures

ErrorCause
verify sign failedWrong sort order, wrong field included/excluded, wrong padding algorithm
parameter validation errormerch_order_id contains non-alphanumeric characters
Wrong signature after refactorbiz_content fields not being flattened before signing
Works in test, fails in productionDifferent RSA key pairs for each environment

Generating RSA keys

# Generate private key
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private-key.pem
 
# Extract public key (share this with Telebirr)
openssl pkey -in private-key.pem -out public-key.pem -pubout

Keep private-key.pem secret. Send public-key.pem to Telebirr during onboarding.