Skip to content

Merchant-Redeem Flow

Use this merchant-redeem flow for server-to-server voucher redemption:

  1. Log in with the merchant API key to receive an x-api-token.
  2. Look up the voucher by code.
  3. Initiate the claim and trigger OTP delivery.
  4. Redeem the gift with the OTP.
  5. Send the external redemption callback when the downstream redemption finishes.

Use the endpoint paths shown in each step.

Response Envelope

Merchant-redeem endpoints return this envelope:

json
{
  "success": true,
  "message": "Successful",
  "statusCode": 200,
  "data": {}
}

Business failures can return HTTP 200 with success: false and an application statusCode in the response body, so branch on success before completing checkout.

Authentication and Signing

HeaderRequiredDescription
x-api-tokenYes, except loginToken returned by POST /api/v1/merchant/client/auth/login.
x-signatureYes for signed POST endpointsBase64 ECDSA P-256 SHA-256 signature over JSON.stringify(request body).
User-AgentNoOptional client identifier.

The GET voucher lookup requires x-api-token only. initiate-claim, redeem-gift, and redeem-gift/callback require both x-api-token and x-signature.

End-to-End Flow

Step 1: Login

POST /api/v1/merchant/client/auth/login

Request Body

FieldTypeRequiredRules
keystringYesMerchant API key configured for the merchant API access record.

Example Request

bash
curl -X POST "$MERCHANT_API_BASE_URL/merchant/client/auth/login" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "merchant_live_xxxxxxxxxxxxx"
  }'

Example Response

json
{
  "accessToken": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
  "success": true,
  "statusCode": 200,
  "message": "Login Successful"
}

Use accessToken as the x-api-token header for the remaining merchant-redeem calls.

Step 2: Look Up Voucher

GET /api/v1/merchant/client/topup-transactions/:voucherCode

Lookup confirms the voucher exists, is redeemable by the authenticated merchant, and shows the current balance and service fee.

Voucher Code Rules

  • Use the 16-digit voucherCode returned by partner voucher issuance.
  • Hyphen-separated display input such as 9876-5432-1098-7654 is accepted and normalized.
  • Do not use the issuance serialNumber (VCH-...) as a redemption code.

Example Request

bash
curl -X GET "$MERCHANT_API_BASE_URL/merchant/client/topup-transactions/9876543210987654" \
  -H "x-api-token: $MERCHANT_API_TOKEN"

Example Response

json
{
  "success": true,
  "message": "Successful",
  "statusCode": 200,
  "data": {
    "voucher": {
      "id": 123,
      "code": "9876543210987654",
      "serialNumber": "VCH-20260427-A1B2C3D4E5F67890",
      "amount": "500.000000",
      "remainingBalance": "350.000000",
      "status": "PARTIALLY_REDEEMED",
      "expirationDate": "2026-07-27T23:59:59.000Z"
    },
    "serviceFee": "1.000000"
  }
}

Voucher status values are UNUSED, PARTIALLY_REDEEMED, REDEEMED, EXPIRED, and REDEEM_INITIATED.

Step 3: Initiate Claim

POST /api/v1/merchant/client/topup-transactions/initiate-claim

Claim initiation creates the pending redemption claim and sends a 6-digit OTP to the voucher owner.

Request Body

FieldTypeRequiredRules
voucherCodestringYesVoucher code to claim. Hyphens are stripped before validation.
amountnumberNoAmount to redeem. Omit to redeem the full remaining balance net of service fee.
idempotencyKeystringNoClient retry key stored in the request body.
metadatastringNoMerchant metadata to persist on the redemption.
merchantIdnumberNoOptional body-level merchant check. Must match the authenticated merchant when provided.
msisdnstringNoOptional voucher owner phone check.
userIdnumberNoOptional merchant user id for audit attribution.
channelstringNoAPI or APP; defaults to API.

Example Request

bash
curl -X POST "$MERCHANT_API_BASE_URL/merchant/client/topup-transactions/initiate-claim" \
  -H "Content-Type: application/json" \
  -H "x-api-token: $MERCHANT_API_TOKEN" \
  -H "x-signature: $MERCHANT_SIGNATURE" \
  -d '{
    "voucherCode": "9876543210987654",
    "amount": 50,
    "idempotencyKey": "merchant-redemption-001",
    "metadata": "{\"checkoutId\":\"CHK-1001\"}",
    "channel": "API"
  }'

Example Response

json
{
  "success": true,
  "message": "Successful",
  "statusCode": 200,
  "data": {
    "message": "OTP Code has been sent to user Successfully",
    "idempotencyKey": "merchant-redemption-001"
  }
}

Step 4: Redeem Gift

POST /api/v1/merchant/client/topup-transactions/redeem-gift

Submit the OTP as verificationCode. A successful response returns referenceNumber and omnivasReferenceNumber; send both back in the callback after external redemption finishes.

Request Body

FieldTypeRequiredRules
voucherCodestringYesVoucher code with optional hyphens.
verificationCodestringYesExactly 6 digits.
idempotencyKeystringNoSame key used for the same redemption retry.
metadatastringNoMerchant metadata to persist on the redemption.
merchantIdnumberNoOptional merchant check; must match the authenticated merchant.
msisdnstringNoOptional voucher owner phone check.
userIdnumberNoOptional merchant user id for audit attribution.
channelstringNoAPI or APP; defaults to API.

Example Request

bash
curl -X POST "$MERCHANT_API_BASE_URL/merchant/client/topup-transactions/redeem-gift" \
  -H "Content-Type: application/json" \
  -H "x-api-token: $MERCHANT_API_TOKEN" \
  -H "x-signature: $MERCHANT_SIGNATURE" \
  -d '{
    "voucherCode": "9876543210987654",
    "verificationCode": "123456",
    "idempotencyKey": "merchant-redemption-001",
    "metadata": "{\"checkoutId\":\"CHK-1001\"}"
  }'

Example Response

json
{
  "success": true,
  "message": "Successful",
  "statusCode": 200,
  "data": {
    "amount": "50.000000",
    "response": "Success",
    "referenceNumber": "red_01HWJ9S8E4Y9G7E4F6N5Q2P3Z8",
    "omnivasReferenceNumber": "7721",
    "remainingBalance": "299.000000",
    "metadata": "{\"checkoutId\":\"CHK-1001\"}",
    "to": "+251911234567"
  }
}

omnivasReferenceNumber is the ODM claim reference. Use it in the callback request.

Step 5: Send Callback

POST /api/v1/merchant/redeem-gift/callback

Use this endpoint to complete the pending external redemption prepared by redeem-gift.

Request Body

FieldTypeRequiredRules
callbackIdstringYesUnique callback id for idempotent callback replay.
omnivasReferenceNumberstringYesClaim reference returned by redeem-gift.
referenceNumberstringNoPending reference returned by redeem-gift.
voucherCodestringYesVoucher code redeemed externally.
statusstringYesSUCCESS or FAILED.
telebirrRefstringRequired for SUCCESSExternal settlement reference.
processedAtstringNoISO timestamp when the external redemption was processed.
amountstringNoRedeemed amount for callback validation.
failureCodestringNoExternal failure code for failed callbacks.
failureReasonstringRequired for FAILEDExternal failure reason.
metadataobject or stringNoAdditional external redemption metadata.

Success Callback Request

bash
curl -X POST "$MERCHANT_API_BASE_URL/merchant/redeem-gift/callback" \
  -H "Content-Type: application/json" \
  -H "x-api-token: $MERCHANT_API_TOKEN" \
  -H "x-signature: $MERCHANT_SIGNATURE" \
  -d '{
    "callbackId": "cb-merchant-001",
    "omnivasReferenceNumber": "7721",
    "referenceNumber": "red_01HWJ9S8E4Y9G7E4F6N5Q2P3Z8",
    "voucherCode": "9876543210987654",
    "status": "SUCCESS",
    "telebirrRef": "TB-240427-000125",
    "processedAt": "2026-04-29T12:03:24.000Z",
    "amount": "50.000000",
    "metadata": {
      "checkoutId": "CHK-1001"
    }
  }'

Success Callback Response

json
{
  "success": true,
  "message": "Successful",
  "statusCode": 200,
  "data": {
    "status": "COMPLETED",
    "claimId": 7721,
    "redemptionId": 9801,
    "telebirrRef": "TB-240427-000125",
    "message": "External redemption success applied."
  }
}

Failed Callback Request

json
{
  "callbackId": "cb-merchant-002",
  "omnivasReferenceNumber": "7721",
  "referenceNumber": "red_01HWJ9S8E4Y9G7E4F6N5Q2P3Z8",
  "voucherCode": "9876543210987654",
  "status": "FAILED",
  "failureCode": "EXTERNAL_DECLINED",
  "failureReason": "External redemption was declined.",
  "amount": "50.000000"
}

Retry and Idempotency

  • Merchant-redeem idempotency uses the optional body field idempotencyKey, not the Idempotency-Key header.
  • Reuse the same idempotencyKey only when retrying the same claim or redemption request.
  • For initiate-claim, a matching pending claim returns the same success message and idempotencyKey.
  • For redeem-gift, a completed or awaiting redemption with the same key returns the stored redemption reference.
  • For callbacks, use a unique callbackId; replaying the same successful callback returns the completed result.

Response Codes

CodeMeaning
200Request processed. Check body success and statusCode for the final result.
401Missing or invalid x-api-token, missing or invalid x-signature, or invalid login key.
4033Verification code missing or no voucher redeem process started.
4035Verification code is not correct.
4037Verification code has expired.
4038Maximum verification attempts exceeded.
4041No voucher found with specified code.
4042Voucher is already redeemed.
4091Amount exceeds voucher balance.
4092Transaction mismatch.
5031Downstream transfer failed.