Whish Pay QR
Generate a Whish Pay payment session that returns a QR payload and a six-digit code the customer scans or enters to pay from their Whish balance.
REST over HTTPS with JSON requests and responses. Intended for authorized distributors.
Integration flow
- 1Create a session. Call Create a payment session with the amount, currency, your
externalId, and optionally acallbackUrl. You receive aqr_payload, a six-digitcode, and apendingstatus. - 2Present the QR or code. Show the customer the QR payload or the six-digit code. They scan the QR or type the code and pay from their Whish balance.
- 3Receive the callback. If you set a
callbackUrl, Whish sends it an HTTP POST with a JSON body after every payment attempt, whether it succeeded or failed. A failed attempt does not close the session: it stays payable until it is either paid or expires. - 4Confirm before fulfilling. Verify the outcome with Get payment status before releasing the order. Poll it as the fallback if a callback did not arrive, or as your primary signal if you did not set a callback.
A status: true response means the session was created and the qr_payload and six-digit code were returned. It does not confirm payment. The payment result (data.status starts as pending) comes only from your callback and/or the payment status endpoint, so confirm it there before fulfilling the order.
Authentication & headers
Every request must include the HTTP headers below, plus Content-Type: application/json on requests that send a body.
| Header | Description |
|---|---|
channel | Merchant channel provided by Whish. |
secret | Merchant secret provided by Whish. |
websiteUrl | Merchant website URL or registered application name. Use the exact value Whish issued with your keys. |
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.
Sandbox payment simulation is not yet available. You can create sessions and integrate the request/response and callback handling in sandbox, but a way to complete a QR payment as a customer is not provided yet. Contact Whish Money for end-to-end test steps.
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. Not to be confused with data.status, the String payment status. |
code | String | null whenever status is true. When status is false, an operation-specific error code; the value 500 is special (see below). Not to be confused with data.code, the numeric code the payer enters. |
dialog | Object | Optional title / message to display to the user. |
retrieved | Boolean | false for a new session; true when an existing active session is returned. |
data | Object | Response payload for the operation; shape varies per endpoint. |
actions / extra | Object | Reserved; typically null. |
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. Escalate to Whish and do not mark it failed. |
false | any other code | Failed, unless that code is a documented challenge for the endpoint you called. 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 payment has progressed. The payment's own lifecycle is data.status on a session, collectStatus on Get payment status, and status in the callback body; a QR session returns status: true with data.status: "pending", which is a successful call for a payment nobody has made yet.
Get account balance
Returns the real balance of the merchant account. Pass currency as a query-string parameter on the request URL to choose which balance to return.
| Parameter | Type | Required | Description |
|---|---|---|---|
currency | String | Required | Currency to return the balance for: USD or LBP. Sent in the query string, for example ?currency=USD. |
Each channel supports two currencies (USD and LBP). Use currency to pick which balance to return.
curl 'https://partner.api.sbx.whish.money/itel-service/api/payment/account/balance?currency=USD' \
-H 'channel: YOUR_CHANNEL' \
-H 'secret: YOUR_SECRET' \
-H 'websiteUrl: https://yoursite.com' \
-H 'User-Agent: AcmeStore/2.1 (https://acme.example; dev@acme.example)'{
"status": true,
"code": null,
"dialog": null,
"actions": null,
"extra": null,
"data": {
"balance": 10439.0
}
}Create a payment session
Creates a Whish Pay payment session from your backend. A successful response returns a session identifier, a six-digit payment code, a QR payload, the expiry time, and the initial session status.
curl -X POST 'https://partner.api.sbx.whish.money/itel-service/api/payment/whish/session' \
-H 'channel: YOUR_CHANNEL' \
-H 'secret: YOUR_SECRET' \
-H 'websiteUrl: https://yoursite.com' \
-H 'User-Agent: AcmeStore/2.1 (https://acme.example; dev@acme.example)' \
-H 'Content-Type: application/json' \
-d '{
"amount": 10.0,
"currency": "USD",
"externalId": "ORDER-2026-0001",
"invoice": "INV-2026-0001",
"callbackUrl": "https://merchant.example/api/whish/payment-success?orderId=ORDER-2026-0001",
"expiry": 10
}'Request body
| Field | Type | Required | Description |
|---|---|---|---|
amount | Double | Required | Payment amount. Must be greater than 0. |
currency | String | Required | Payment currency: USD or LBP. |
externalId | String | Required | Unique merchant transaction reference; must not be blank. Reuse the same value to return the existing active session (idempotency); a new payment needs a new value. |
invoice | String | Optional | Invoice or order reference. |
callbackUrl | String | Optional | Payment-result callback URL. Whish POSTs to it after every payment attempt, successful or failed. Must be a valid, reachable URL. |
expiry | Integer | Optional | Requested session lifetime in minutes. Effective range 5–30 (see Session behavior). |
{
"amount": 10.0,
"currency": "USD",
"externalId": "ORDER-2026-0001",
"invoice": "INV-2026-0001",
"callbackUrl": "https://merchant.example/api/whish/payment-success?orderId=ORDER-2026-0001",
"expiry": 10
}{
"status": true,
"code": null,
"dialog": null,
"actions": null,
"extra": null,
"retrieved": false,
"data": {
"session_id": 123456789,
"code": "583921",
"qr_payload": "https://api.sandbox.whish.money/pay/AbC123xY",
"expiry_time": "2026-06-30T12:10:00Z",
"status": "pending"
}
}Session data
| Field | Type | Description |
|---|---|---|
session_id | Long | Whish payment session identifier, for your records and support queries. You do not need it to check status or receive callbacks; those key off your externalId. |
code | String | Six-digit payment code. |
qr_payload | String | Whish Pay deeplink to encode as a QR code. |
expiry_time | Date/Time | Session expiry date and time. |
status | String | Initial value is pending. |
qr_payload is the exact value to encode as a QR code. The API returns the payload string, not a PNG, SVG, or Base64 image. The six-digit code and the QR payload refer to the same session, share the same expiry time, and a session can be completed only once.
The callbackUrl must be publicly reachable. Sending localhost (or any loopback or non-public host) is not allowed: the request is rejected with HTTP 403 Forbidden. Use a public URL.
Get payment status
Returns the collect status of the session. Use it to confirm the outcome when a callback did not arrive, and to tell a settled outcome from an open one: a failure callback reports one failed attempt and leaves the session payable, so this endpoint is what confirms a session finally failed.
curl -X POST 'https://partner.api.sbx.whish.money/itel-service/api/payment/collect/status' \
-H 'channel: YOUR_CHANNEL' \
-H 'secret: YOUR_SECRET' \
-H 'websiteUrl: https://yoursite.com' \
-H 'User-Agent: AcmeStore/2.1 (https://acme.example; dev@acme.example)' \
-H 'Content-Type: application/json' \
-d '{ "currency": "USD", "externalId": "ORDER-2026-0001" }'Request body
| Field | Type | Required | Description |
|---|---|---|---|
currency | String | Required | Currency: USD or LBP. |
externalId | String | Required | The externalId you sent when creating the session. |
{
"currency": "USD",
"externalId": "ORDER-2026-0001"
}{
"status": true,
"code": null,
"dialog": null,
"actions": null,
"extra": null,
"data": {
"collectStatus": "success",
"payerPhoneNumber": "96170123456"
}
}Response fields
| Field | Type | Description |
|---|---|---|
collectStatus | String | Session status: pending, success, failed, refunded, or unknown. |
payerPhoneNumber | String | Phone number that performed the payment. |
Session behavior
QR and code
qr_payloadis the exact value to encode as a QR code.- The API returns the payload, not a PNG, SVG, or Base64 image.
- The six-digit code and the QR payload refer to the same session and share the same expiry time.
- A session can be completed only once.
Expiry
| Submitted value | Effective expiry |
|---|---|
Not provided or null | 5 minutes |
| Less than 5 | 5 minutes |
| Between 5 and 30 | Submitted value |
| Greater than 30 | 30 minutes |
After expiry, the QR payload and code must no longer be used. A new payment attempt must use a new externalId.
Duplicate externalId
| Existing condition | Result |
|---|---|
| Active session with the same amount | Existing session returned with retrieved: true. |
| Existing session with a different amount | external_id.already_used |
| Successful, failed, or refunded session | whishpay.tx.invalid_state |
| Expired session | collect.expired |
Supported statuses
| Status | Description |
|---|---|
pending | Waiting for customer payment. |
success | Payment completed successfully. |
failed | The session expired without being paid. A single failed attempt does not land here: the session stays pending and payable until it is paid or expires. |
refunded | Successful payment was refunded. |
unknown | Final state could not be determined. |
A QR payment is a Whish Pay collection, so refunds are issued with the Whish Pay Refund endpoint (/payment/whish/refund) using the same externalId. Once refunded, the session status here becomes refunded. See Whish Pay → Refund a payment.
Callbacks
When callbackUrl is provided, Whish reports the outcome of every payment attempt on the session to it.
A failure callback is not the end of the payment. A QR session stays payable until it is either paid successfully or expires, so a customer whose attempt failed can simply scan or enter the same code again. That is why Get payment status keeps reporting pending after a failure callback: the payment can still succeed. Only a session that expires without a successful payment settles as failed. So treat a failure callback as this attempt failed, never as this order failed, and do not cancel the order on it.
- Whish calls it after every attempt: once with
status: "success"when the session is paid, and once withstatus: "failed"for each attempt that fails. - You get one callback per attempt, so a session paid on the second try produces a failed callback followed by a success callback.
- The callback uses HTTP
POSTwithContent-Type: application/json. - The request body carries the payment result; both shapes are identical apart from
status. - Query parameters already included in the merchant URL are preserved.
- Respond with HTTP 200 to acknowledge receipt.
https://merchant.example/api/whish/payment-result?orderId=ORDER-2026-0001{
"status": "success",
"amount": 10000,
"currency": "LBP",
"timestamp": 1787299542710,
"external_id": "17872994498120050",
"session_id": 25257080
}{
"status": "failed",
"amount": 10000,
"currency": "LBP",
"timestamp": 1787299530415,
"external_id": "17872994498120050",
"session_id": 25257080
}Callback body
| Field | Type | Description |
|---|---|---|
status | String | Result of this attempt: success or failed. failed describes the attempt, not the session, which stays payable until paid or expired. |
amount | Number | Amount of the session, in currency. |
currency | String | Payment currency: USD or LBP. |
timestamp | Long | When the attempt was recorded, as a Unix timestamp in milliseconds (not an ISO date string). |
external_id | String | The externalId you sent when creating the session. This is your key for matching the callback to an order. |
session_id | Long | Whish payment session identifier, for your records and support queries. |
Because a failure callback is not a settled outcome, confirm with Get payment status before releasing an order, and use it to reconcile if a callback did not arrive (for example a transient network failure) or if the session expired unpaid, which is reported by status rather than by a callback. A missing callback should be reconciled by polling status, not treated as a failed payment.
Whish calls your callbackUrl from a fixed set of source IPs. If your callback endpoint restricts inbound traffic, allowlist them; the list is maintained in the Reliability & Idempotency guide.
Error codes
{
"status": false,
"code": "amount.invalid",
"dialog": {
"title": "Error",
"message": "Amount must be greater than zero"
},
"actions": null,
"extra": null,
"data": null
}Core codes
| Code | Description |
|---|---|
invalid.request | Request body is missing. |
400 | Mandatory authentication/request parameters are missing, or request field validation failed. |
auth.session_not_exist | Missing, invalid, expired, or mismatched merchant session, token, or websiteUrl. |
auth.session_expired | Merchant session token is expired. Re-authenticate or request a new session. |
external_id.required | externalId is missing or blank. |
currency.required | currency is missing or blank. |
amount.invalid | Amount is missing, zero, or negative. |
currency.not_supported | Currency is not USD or LBP. |
external_id.already_used | External ID is linked to another amount or invalid session. |
whishpay.tx.invalid_state | Existing session is in a final state such as successful, failed, or refunded. |
collect.expired | Existing session has expired. Use a new externalId. |
collect.wrong_denomination | Merchant payment denomination is not configured. |
Additional error codes
| Code | Description |
|---|---|
collect.wrong_phone_format | Phone number could not be normalized or validated. |
collect.wrong_email_format | Email value is present but invalid. |
refresh.denominations | Configured denomination is unavailable or not refreshed on the server. |
credit_card_payment.account_verification_required | Merchant account must be verified before this transaction can proceed. |
whish_pay.missing_pricing_id | Whish Pay pricing is not configured for the merchant profile. |
whish_pay.code_generation_failed | Payment code could not be generated or retrieved. |
purchase.confirm | Quoted price changed before confirmation. Re-quote and resubmit with the new price. |
fail | Pricing or amount computation failed. Check the returned dialog for details. |
sales.account_balance_insufficient | Merchant account balance cannot cover the transaction amount. |
sales.account_balance_insufficient_no_balance | Merchant account balance is insufficient on the third-party sale route. |
account.terminated | Calling account is terminated. |
account.dealer.terminated | Parent dealer account is terminated. |
sales.denomination_not_active | Requested service or denomination is disabled. |
sales.schema_not_active | Pricing schema is not configured or active for this account and service. |
sales.exceeded_daily_limit | Daily amount or count limit has been reached. |
sales.exceeded_monthly_limit | Monthly amount or count limit has been reached. |
sales.lost_transactions_exist | Previous transaction results must be checked before submitting a new transaction. |
emoji.not_supported | Payload contains unsupported characters such as emoji. |
collect_qr.general_error | QR deeplink could not be generated or retrieved. |
500 | Unhandled server-side exception. |
itel.unknown_error | Unexpected internal error or generic fallback. |
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.0 | 2026-07-15 | Pre-release documentation update: callback aligned to HTTP POST with a JSON body; error codes expanded; externalId documented as a String; sandbox base URL updated. |
v1.0 | 2026-06-30 | Initial release for the Whish Pay payment session endpoint. |
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.