Whish Money
Whish Money
Partner Integration Guide

Vouchers

Purchase digital vouchers (gaming, streaming, gift cards, and recharge PINs) and receive the serial and secret instantly.

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

v1.3.1Platform: WhishJSON over HTTPSAuthor: TecFrac
Authentication
sessionId + token
Production base URL
https://api.whish.money/itel-service/api
Currencies
USD · LBP · AED · OMR

Integration flow

  1. 1
    Purchase a voucher. Call Purchase a voucher with the chosen denomination and a unique externalId.
  2. 2
    Read the codes. The response returns the sold item synchronously, including the serialNumber and secretCode. Deliver these to your customer.
  3. 3
    Retry safely. Resend the same externalId to retry; an already-processed purchase replays the same codes and never charges twice.

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).

Keep your credentials secret. They authorize account actions and money movement, so send them only over HTTPS, store them in a secure secrets manager, and never expose them in client-side code, logs, or version control. Rotate them immediately if you suspect a leak.

Environments

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

The cURL examples on this page target Sandbox. To go live, swap the host for the Production base URL above.

Response format

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

FieldTypeDescription
statusBooleantrue when the call was accepted and processed. false when it was not; read code for the reason.
codeStringnull whenever status is true. When status is false, an operation-specific error code; the value 500 is special (see below). This field only ever reports errors, never a transaction's progress.
dialogObjectOptional title / message to display to the user.
dataObjectResponse payload for the operation; shape varies per endpoint (and may be a scalar or null for some operations).
actions / extraObjectReserved; typically null.
retrievedBooleanPresent on idempotent operations. true means this response replays an already-processed request (the original was not run again); false or absent means it ran now.

The two fields move together, so read them as a pair. There are only these combinations:

statuscodeTreat as
truenullSucceeded. Process data.
false500Pending. The call did not return an outcome, so the result is unknown rather than failed. Reconcile as described below; do not mark it failed.
falseany other codeFailed, unless that code is a documented challenge for the endpoint you called (for example a required provider OTP). Handle documented challenges as an expected step in the flow, not as a failure; see the relevant endpoint reference.

A status: true is never accompanied by a code, and a code never reports how far a transaction has progressed. Where an operation has a lifecycle of its own (created, paid, refunded), the endpoint reports it on a separate field inside data; see that endpoint's response fields.

No separate status endpoint. To reconcile after a timeout or a code: 500 pending result, resend the same request with the same externalId. An already-processed request returns its original result with retrieved: true, so you recover the outcome without charging again.

Get account balance

Returns the balance of the account, including the value currently held as voided vouchers.

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 more than one currency, use a separate session for each.

Example request · cURL
cURL · Sandbox
curl 'https://partner.api.sbx.whish.money/itel-service/api/account/balance' \
  -H 'sessionId: YOUR_SESSION_ID' \
  -H 'token: YOUR_TOKEN' \
  -H 'language: en' \
  -H 'User-Agent: AcmeStore/2.1 (https://acme.example; dev@acme.example)'
Response · 200 success
application/json
{
  "status": true,
  "code": null,
  "dialog": null,
  "actions": null,
  "extra": null,
  "data": {
    "balanceDetails": {
      "balance": 999790.646,
      "voidBalance": 0.0
    }
  }
}

Response fields (in data.balanceDetails)

FieldTypeDescription
balanceDoubleThe real balance of the account, in the session's currency.
voidBalanceDoubleValue of your voided items, held for reuse on a future purchase of the same product. Not a refundable cash balance, and only meaningful where there is stock to hold, so it stays 0.0 for services such as bills and airtime top-ups.

A non-zero voidBalance means you hold voided vouchers that your next purchase of the same denomination will draw on before charging you. See Voiding vouchers.

Purchase a voucher

Purchases one or more vouchers of the given denomination and returns the item data (serial & secret).

POST/sale/item
Example request · cURL
cURL · Sandbox
curl -X POST 'https://partner.api.sbx.whish.money/itel-service/api/sale/item' \
  -H 'sessionId: YOUR_SESSION_ID' \
  -H 'token: YOUR_TOKEN' \
  -H 'language: en' \
  -H 'User-Agent: AcmeStore/2.1 (https://acme.example; dev@acme.example)' \
  -H 'Content-Type: application/json' \
  -d '{ "externalId": "987654", "denominationId": 17002, "numberOfItems": 1 }'

Request body

FieldTypeRequiredDescription
externalIdStringRequiredUnique per purchase (idempotency key). Reuse the same value to retry the same purchase.
denominationIdintRequiredDenomination of the requested item.
numberOfItemsintRequiredNumber of vouchers requested. A purchase is all-or-nothing: if the full quantity is not in stock, none are sold and sales.requested_quantity_not_available_in_our_stock is returned.
Request
Request body
{
  "externalId": "987654",
  "denominationId": 17002,
  "numberOfItems": 1
}
Response · 200 success
application/json
{
  "status": true,
  "code": null,
  "dialog": null,
  "actions": null,
  "extra": null,
  "data": {
    "listOfSoldItems": [
      {
        "date": "2026-07-20 14:34:18",
        "transactionId": 20224990,
        "service": "ITUNES USA",
        "denomination": "iTUNES $2 - USA",
        "denominationValue": 2.0,
        "validityPeriod": null,
        "expiryDate": "2027-06-13",
        "itemId": 64399932,
        "sessionCounter": 1,
        "serialNumber": "SBXITN8JBCXGOSUSNSC2PX",
        "secretCode": "SBXSEC4AXLW54V7YO2MSMX2J",
        "help": "",
        "contact": "+961 1 788 999",
        "type": 5,
        "picture": "iconitunes",
        "category": 0,
        "fromVoid": false,
        "printType": 1,
        "viewType": 1
      }
    ]
  }
}
Replay after timeout (same externalId)
application/json
{
  "status": true,
  "code": null,
  "dialog": null,
  "actions": null,
  "extra": null,
  "retrieved": true,
  "data": {
    "listOfSoldItems": [
      {
        "date": "2026-07-20 14:34:18",
        "transactionId": 20224990,
        "service": "ITUNES USA",
        "denomination": "iTUNES $2 - USA",
        "denominationValue": 2.0,
        "validityPeriod": null,
        "expiryDate": "2027-06-13",
        "itemId": 64399932,
        "sessionCounter": 1,
        "serialNumber": "SBXITN8JBCXGOSUSNSC2PX",
        "secretCode": "SBXSEC4AXLW54V7YO2MSMX2J",
        "help": "",
        "contact": "+961 1 788 999",
        "type": 5,
        "picture": "iconitunes",
        "category": 0,
        "fromVoid": false,
        "printType": 1,
        "viewType": 1
      }
    ]
  }
}

Response fields (listOfSoldItems[])

FieldTypeDescription
serialNumberStringThe voucher serial to deliver to the customer.
secretCodeStringThe voucher PIN/secret to deliver to the customer. Treat it as sensitive.
transactionIdLongWhish transaction identifier for this purchase.
denominationStringDenomination name of the voucher sold.
denominationValueDoubleFace value of the voucher in the account's billing currency.
serviceStringProduct family (for example ITUNES USA).
expiryDateStringVoucher expiry date (YYYY-MM-DD).
helpStringRedemption instructions to show the customer, when the product provides them; empty otherwise.
contactStringSupport contact to show the customer, when the product provides one.
dateStringSale timestamp (YYYY-MM-DD HH:MM:SS).
fromVoidBooleantrue if this item came from previously voided stock, so the purchase drew on your void balance instead of being charged as new stock.
Additional fields returned (7)
FieldTypeDescription
itemIdLongIdentifier of the individual sold item.
validityPeriodStringValidity period of the voucher, when the product defines one; otherwise null.
sessionCounterintPosition of this item within the purchase, starting at 1. With numberOfItems above 1, each returned item carries its own value.
typeintProduct type code assigned by Whish.
categoryintProduct category code assigned by Whish.
pictureStringProduct artwork name, for display.
printType / viewTypeintPresentation codes assigned by Whish for printing and display.
Response failure
application/json
{
  "status": false,
  "code": "sales.requested_quantity_not_available_in_our_stock",
  "dialog": {
    "title": "Error",
    "message": "Sorry, the requested quantity is not available in our stock right now."
  },
  "actions": null,
  "extra": null,
  "data": null
}

Test denominations

Denomination IDDenomination name
17002iTUNES 2$ - USA
17003iTUNES 3$ - USA
17005iTUNES 5$ - USA
40502DU PIN (AED 25)
40503DU PIN (AED 55)
40504DU PIN (AED 110)

A denomination's face value may be shown in the product's own currency (for example AED for a DU PIN, or USD for an iTUNES card). Your account is billed in its configured currency, and denominationValue in the response is the value in that billing currency.

Voiding vouchers

Voiding sets a purchased voucher aside for reuse instead of cancelling it. It is not a refund: no money moves, and no credit note is issued. The voucher's value stays with your account as void balance, and the item goes back to the front of the queue for its denomination, so your next purchase of that same denominationId hands you the voided voucher instead of charging you for new stock.

Requesting a void

Voiding is done by Whish, not through this API. Contact the Whish After Sales department with either the transactionId or the serialNumber of the voucher you want voided, both of which are returned in listOfSoldItems[] when you purchase. Store them with your order records so you can identify a voucher later.

What to void

SituationWhat to do
A voucher you bought by mistake and have not delivered to a customerVoid it. This is the case voiding exists for, and it is always worth doing rather than writing the voucher off.
A voucher whose outcome you could not determine (for example a timeout, or unknown)Void it as well. If it turns out the voucher was already used, the customer who receives it next will find it already redeemed, and that tells you the original sale went through. Nothing is lost by voiding first.

How a voided voucher comes back

Voided items are consumed before new stock, per denomination. A purchase can therefore be filled from both: if you hold 3 voided vouchers of a denomination and request 10, you receive 10 vouchers, of which 3 come from your voided items and 7 are sold as new. You are charged for the 7 only. Each item that came back this way is flagged with fromVoid: true, so you can reconcile the charge against the response.

Match on the denomination: a voided voucher is only reused by a later purchase of the same denominationId. It is not applied to a different denomination, and it does not expire into cash.

Error codes

Every code below is returned with status: false. Match the string exactly and branch on it, never on the dialog message text, which is written for display and can change.

Re-sending an already-used externalId never re-executes the transaction: the recorded result is replayed verbatim, including the original error code (safe retry, no double charge).

Vouchers codes

CodeMeaningRecommended handling
sales.account_balance_insufficient_no_balanceInsufficient balance for the purchase (the _no_balance suffix is appended on this route).Top up, then retry with a new externalId.
sales.requested_quantity_not_available_in_our_stockNot enough vouchers in stock for numberOfItems.Reduce the quantity or retry later.
sales.denomination_not_activeThe voucher denomination is disabled.Verify the denomination id; retry later.
sales.denomination_not_enabledThe item is temporarily not enabled for sale.Retry later.
sales.daily_transaction_count_limit_reachedPer-denomination daily voucher count limit reached.Wait until next day or request an increase.
sales.exceeded_daily_limit / sales.exceeded_monthly_limitAggregate sale limits reached.Wait, or request an increase.
raw exception textAn unhandled internal exception; the code carries a non-stable internal message.Treat as an opaque failure; retry with the same externalId, escalate if persistent.

Common codes (all payment & billing endpoints)

Authentication, session, and request validation:

CodeMeaningRecommended handling
auth.session_not_existMissing or invalid session token.Re-authenticate, then retry.
auth.session_expiredSession token expired.Re-authenticate, then retry.
400Required parameters or headers are missing, or the body failed field-level validation.Fix the request payload; do not retry unchanged.
timestamp.invalidRequest timestamp is outside the allowed clock-skew window.Sync the client clock (NTP), then retry.
request.invalid / device.invalid_osDevice or channel-level validation rejected the request.Verify your integration headers; contact Whish if persistent.
500Unhandled server-side exception.Retry with backoff using the same externalId to avoid double execution; report if persistent.
itel.unknown_errorGeneric fallback for unexpected errors or server misconfiguration.Retry with backoff; escalate if persistent.
emoji.not_supportedPayload contained characters the database rejects (for example emoji).Strip unsupported characters, then retry.
deprecationThe endpoint version is deprecated.Migrate to the current endpoint version.

Account, balance, and limits (any debiting endpoint):

CodeMeaningRecommended handling
sales.account_balance_insufficientAccount balance cannot cover the amount.Top up the account, then retry with a new externalId.
sales.account_balance_insufficient_no_balanceSame insufficient-balance condition; variant returned on third-party sale routes.Top up, then retry with a new externalId.
account.terminatedThe calling account has been terminated.Contact Whish (not recoverable client-side).
account.dealer.terminatedThe parent dealer account is terminated.Contact Whish.
sales.denomination_not_activeThe requested service/denomination is disabled.Verify the denomination id; retry later or contact Whish.
sales.schema_not_activePricing schema not configured/active for this account and service.Contact Whish (configuration issue).
sales.exceeded_daily_limitDaily amount/count limit reached.Wait for the next day, or request a limit increase.
sales.exceeded_monthly_limitMonthly limit reached.Wait, or request a limit increase.
sales.lost_transactions_existEarlier transaction results were not acknowledged.Query the status of prior transactions (replay their externalIds) before submitting new ones.
account.topup.check.condition.fail (surfaced as transfer.failed.title)The receiving account would exceed its maximum allowed wallet balance.Final failure; the receiver must reduce balance or verify their account.
currency.not_supported / invalid_currencyThe currency is not enabled or recognized for this route (typically only LBP and USD).Send a supported currency.
fatalInternal failure while registering the externalId (defensive path; not expected in practice).Retry with the same externalId; escalate if persistent.

Version history

Notable changes to this API and its reference, newest first. The current version is the one shown at the top of this page.

VersionDateChange
v1.3.12026-07-15Documentation: error-code reference added; externalId documented as a String; sandbox base URL updated.
v1.32026-05-14Base URL update, test denominations, and User-Agent requirement.
v1.22020-12-01New Voucher API.

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. The OpenAPI 3.1 specification is also available to generate client SDKs or validate requests.

Download collectionOpenAPI specAll 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.