Whish Money
Whish Money
Partner Integration Guide

Bills

Fetch and pay real-time bills (utilities, telecom, tuition, and more) with an OTP-verified fetch flow.

REST over HTTPS with JSON requests and responses. Intended for authorized distributors.

Version 1.2WhishJSON over HTTPSAuthor: TecFrac
🧾

Two-step flow: Fetch Bill returns the amount to pay (some billers require a provider OTP first), then Pay Bill settles it using the exact billId and baseAmount returned by fetch.

Authentication & headers

Every request must include the HTTP headers below, plus Content-Type: application/json on requests that send a body.

HeaderDescription
sessionIdValue provided by Whish.
tokenValue provided by Whish.
languageAlways set to en.
User-AgentIdentifies your app. Use your own details, not Whish's. Format: AppName/version (website; contact-email). Example: AcmeStore/2.1 (https://acme.example; dev@acme.example).

Environments

EnvironmentBase URLPurpose
Sandboxhttps://api.sandbox.whish.money/itel-service/apiTesting, QA, integration verification.
Productionhttps://api.whish.money/itel-service/apiLive environment for real transactions.

Response format

All endpoints return HTTP 200 with Content-Type: application/json and a shared envelope. Always branch on the status field in the body, not the HTTP status code.

FieldTypeDescription
statusBooleantrue = success · false = failure.
codeStringOperation-specific failure code; null on success.
dialogObjectOptional title / message to display to the user.
dataObjectResponse payload for the operation; shape varies per endpoint.
actions / extraObjectReserved; typically null.

Account balance

Returns the real and void balances of the account. The void balance represents voided items (sold but unused, held for later).

GET/account/balance
ℹ️

No parameters. Each session (sessionId) is tied to a single currency, so the balance is always returned in that session's currency. To work in both LBP and USD, use a separate session for each.

Response · 200 success
application/json
{
  "status": true,
  "code": null,
  "dialog": null,
  "extra": null,
  "data": {
    "balanceDetails": {
      "balance": -217.718,
      "voidBalance": 0.0
    }
  }
}

Response fields

FieldTypeDescription
balanceDoubleThe real balance of the account.
voidBalanceDoubleThe void balance of the account.

Fetch bill

Fetches a bill. For most denominations this is a two-step, OTP-verified flow; selected denominations skip OTP.

POST/bill/fetch

Request body

FieldTypeDescription
denominationIdintIdentifier of the product/biller.
targetStringBill target (e.g. subscriber phone number).
verificationIdintOTP session ID from Step 1; include only in Step 2 with otp.
otpintOne-time password sent by the carrier; required in Step 2.

Standard flow: OTP first

ℹ️

Treat code = bill.requires_provider_otp as an expected challenge, not an error. Persist verificationId = data and prompt the user for the OTP. Sandbox fixed values: verificationId = 6789, otp = 1234.

Step 1: request (denominationId 53001)
Request body
{
  "denominationId": 53001,
  "target": "971555555555"
}
Step 1: response failure
application/json
{
  "status": false,
  "code": "bill.requires_provider_otp",
  "dialog": {
    "title": "Verification required",
    "message": "Please enter the OTP sent by the provider."
  },
  "actions": null,
  "extra": null,
  "data": 6789
}
Step 2: submit OTP
Request body
{
  "denominationId": 53001,
  "target": "971555555555",
  "verificationId": 6789,
  "otp": 1234
}
Step 2: response success
application/json
{
  "status": true,
  "code": null,
  "dialog": null,
  "actions": null,
  "extra": null,
  "data": {
    "billId": 1220,
    "target": "0555555555",
    "baseAmount": 19.0,
    "additionalAmount": -0.0,
    "totalAmount": 19.0,
    "allowPayAll": true,
    "minAmount": 19.0,
    "maxAmount": 19.0,
    "amountIncrement": 0.0
  }
}

Alternative flow: no OTP (selected denominations)

Request (denominationId 54001)
Request body
{
  "denominationId": 54001,
  "target": "971555555555"
}
Response success
application/json
{
  "status": true,
  "code": null,
  "dialog": null,
  "extra": null,
  "data": {
    "billId": 1234,
    "target": "971555555555",
    "baseAmount": 30.40,
    "additionalAmount": 1.60,
    "totalAmount": 32.00,
    "allowPayAll": true,
    "minAmount": null,
    "maxAmount": null,
    "amountIncrement": null
  }
}

Pay bill

Pays the bill retrieved by Fetch Bill. Always pass the exact billId and baseAmount (as amount) returned by fetch.

POST/bill/payment

Request body

FieldTypeDescription
externalIdLongClient-generated unique ID per payment attempt (idempotency).
billIdLongThe billId returned by /bill/fetch.
amountDoubleMust equal baseAmount from /bill/fetch.
Request
Request body
{
  "externalId": 9876543210,
  "billId": 1220,
  "amount": 19.0
}
Response · 200 success
application/json
{
  "status": true,
  "code": null,
  "dialog": null,
  "extra": null
}
Response failure
application/json
{
  "status": false,
  "code": "payment.failed",
  "dialog": {
    "title": "Error",
    "message": "Payment failed"
  },
  "extra": null
}

Error codes

CodeDescription
bill.requires_provider_otpOTP challenge (expected on fetch, not a failure).
bill.fetch_failedBill fetch failed.
payment.failedPayment failed.

Sandbox scenarios

Denomination IDBehavior
53001Standard fetch & payment (requires provider OTP).
54001Fetch & payment (no OTP).
Test targetExpected outcome
971555555555Success path (fetch and pay).
971555555554Fetch fails (bill.fetch_failed).
971555555553Fetch succeeds; payment fails (payment.failed).
🔑

Fixed sandbox OTP values: verificationId = 6789, otp = 1234.

Best practices

  • Always include sessionId, token, language headers.
  • Treat bill.requires_provider_otp as a two-step flow, not an error; persist verificationId.
  • Use the exact billId and baseAmount from fetch when paying.
  • Make externalId unique per payment attempt (client-side idempotency).
  • Normalize amounts to two decimals for display; preserve full precision internally.
  • Handle transient failures with safe retries; on payment retry use a new externalId unless told otherwise.

Postman & tools

Import the ready-made Postman collection for this API, set your credentials as collection variables (or attach a sandbox / production environment), and call every endpoint without writing code.

⤓ Download collectionAll downloads & environments

ℹ️

In Postman: Import → select the file → choose the Sandbox or Production environment → fill in your credentials. Requests use {{baseUrl}} and the credential variables provided.