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.
Integration flow
- 1Purchase a voucher. Call Purchase a voucher with the chosen denomination and a unique
externalId. - 2Read the codes. The response returns the sold item synchronously, including the
serialNumberandsecretCode. Deliver these to your customer. - 3Retry safely. Resend the same
externalIdto 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.
| Header | Description |
|---|---|
sessionId | Value provided by Whish. |
token | Value provided by Whish. |
language | Always set to en. |
User-Agent | Identifies 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
| Environment | Base URL | Purpose |
|---|---|---|
| Sandbox | https://partner.api.sbx.whish.money/itel-service/api | Testing, QA, integration verification. |
| Production | https://api.whish.money/itel-service/api | Live 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.
| Field | Type | Description |
|---|---|---|
status | Boolean | true when the call was accepted and processed. false when it was not; read code for the reason. |
code | String | null 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. |
dialog | Object | Optional title / message to display to the user. |
data | Object | Response payload for the operation; shape varies per endpoint (and may be a scalar or null for some operations). |
actions / extra | Object | Reserved; typically null. |
retrieved | Boolean | Present 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:
status | code | Treat as |
|---|---|---|
true | null | Succeeded. Process data. |
false | 500 | Pending. The call did not return an outcome, so the result is unknown rather than failed. Reconcile as described below; do not mark it failed. |
false | any other code | Failed, 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.
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.
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)'{
"status": true,
"code": null,
"dialog": null,
"actions": null,
"extra": null,
"data": {
"balanceDetails": {
"balance": 999790.646,
"voidBalance": 0.0
}
}
}Response fields (in data.balanceDetails)
| Field | Type | Description |
|---|---|---|
balance | Double | The real balance of the account, in the session's currency. |
voidBalance | Double | Value 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).
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
| Field | Type | Required | Description |
|---|---|---|---|
externalId | String | Required | Unique per purchase (idempotency key). Reuse the same value to retry the same purchase. |
denominationId | int | Required | Denomination of the requested item. |
numberOfItems | int | Required | Number 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. |
{
"externalId": "987654",
"denominationId": 17002,
"numberOfItems": 1
}{
"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
}
]
}
}{
"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[])
| Field | Type | Description |
|---|---|---|
serialNumber | String | The voucher serial to deliver to the customer. |
secretCode | String | The voucher PIN/secret to deliver to the customer. Treat it as sensitive. |
transactionId | Long | Whish transaction identifier for this purchase. |
denomination | String | Denomination name of the voucher sold. |
denominationValue | Double | Face value of the voucher in the account's billing currency. |
service | String | Product family (for example ITUNES USA). |
expiryDate | String | Voucher expiry date (YYYY-MM-DD). |
help | String | Redemption instructions to show the customer, when the product provides them; empty otherwise. |
contact | String | Support contact to show the customer, when the product provides one. |
date | String | Sale timestamp (YYYY-MM-DD HH:MM:SS). |
fromVoid | Boolean | true 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)
| Field | Type | Description |
|---|---|---|
itemId | Long | Identifier of the individual sold item. |
validityPeriod | String | Validity period of the voucher, when the product defines one; otherwise null. |
sessionCounter | int | Position of this item within the purchase, starting at 1. With numberOfItems above 1, each returned item carries its own value. |
type | int | Product type code assigned by Whish. |
category | int | Product category code assigned by Whish. |
picture | String | Product artwork name, for display. |
printType / viewType | int | Presentation codes assigned by Whish for printing and display. |
{
"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 ID | Denomination name |
|---|---|
17002 | iTUNES 2$ - USA |
17003 | iTUNES 3$ - USA |
17005 | iTUNES 5$ - USA |
40502 | DU PIN (AED 25) |
40503 | DU PIN (AED 55) |
40504 | DU 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
| Situation | What to do |
|---|---|
| A voucher you bought by mistake and have not delivered to a customer | Void 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
| Code | Meaning | Recommended handling |
|---|---|---|
sales.account_balance_insufficient_no_balance | Insufficient 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_stock | Not enough vouchers in stock for numberOfItems. | Reduce the quantity or retry later. |
sales.denomination_not_active | The voucher denomination is disabled. | Verify the denomination id; retry later. |
sales.denomination_not_enabled | The item is temporarily not enabled for sale. | Retry later. |
sales.daily_transaction_count_limit_reached | Per-denomination daily voucher count limit reached. | Wait until next day or request an increase. |
sales.exceeded_daily_limit / sales.exceeded_monthly_limit | Aggregate sale limits reached. | Wait, or request an increase. |
| raw exception text | An 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:
| Code | Meaning | Recommended handling |
|---|---|---|
auth.session_not_exist | Missing or invalid session token. | Re-authenticate, then retry. |
auth.session_expired | Session token expired. | Re-authenticate, then retry. |
400 | Required parameters or headers are missing, or the body failed field-level validation. | Fix the request payload; do not retry unchanged. |
timestamp.invalid | Request timestamp is outside the allowed clock-skew window. | Sync the client clock (NTP), then retry. |
request.invalid / device.invalid_os | Device or channel-level validation rejected the request. | Verify your integration headers; contact Whish if persistent. |
500 | Unhandled server-side exception. | Retry with backoff using the same externalId to avoid double execution; report if persistent. |
itel.unknown_error | Generic fallback for unexpected errors or server misconfiguration. | Retry with backoff; escalate if persistent. |
emoji.not_supported | Payload contained characters the database rejects (for example emoji). | Strip unsupported characters, then retry. |
deprecation | The endpoint version is deprecated. | Migrate to the current endpoint version. |
Account, balance, and limits (any debiting endpoint):
| Code | Meaning | Recommended handling |
|---|---|---|
sales.account_balance_insufficient | Account balance cannot cover the amount. | Top up the account, then retry with a new externalId. |
sales.account_balance_insufficient_no_balance | Same insufficient-balance condition; variant returned on third-party sale routes. | Top up, then retry with a new externalId. |
account.terminated | The calling account has been terminated. | Contact Whish (not recoverable client-side). |
account.dealer.terminated | The parent dealer account is terminated. | Contact Whish. |
sales.denomination_not_active | The requested service/denomination is disabled. | Verify the denomination id; retry later or contact Whish. |
sales.schema_not_active | Pricing schema not configured/active for this account and service. | Contact Whish (configuration issue). |
sales.exceeded_daily_limit | Daily amount/count limit reached. | Wait for the next day, or request a limit increase. |
sales.exceeded_monthly_limit | Monthly limit reached. | Wait, or request a limit increase. |
sales.lost_transactions_exist | Earlier 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_currency | The currency is not enabled or recognized for this route (typically only LBP and USD). | Send a supported currency. |
fatal | Internal 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.
| Version | Date | Change |
|---|---|---|
v1.3.1 | 2026-07-15 | Documentation: error-code reference added; externalId documented as a String; sandbox base URL updated. |
v1.3 | 2026-05-14 | Base URL update, test denominations, and User-Agent requirement. |
v1.2 | 2020-12-01 | New 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.