Whish Money
Whish Money
Partner Integration Guide

Whish Pay

Collect payments from your customers through their Whish balance. Generate a hosted payment page, track status, and issue refunds.

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

v1.4.4Platform: WhishJSON over HTTPSAuthor: TecFrac
Authentication
channel + secret + websiteUrl
Production base URL
https://api.whish.money/itel-service/api
Currencies
USD · LBP

Integration flow

  1. 1
    Create a payment. Call Create a payment with the amount, currency, your externalId, and your callback URLs. You receive a collectUrl.
  2. 2
    Redirect the payer. Send the customer to the collectUrl. On that hosted page the payer authorizes the payment from their Whish balance and confirms with a one-time code (OTP) that Whish delivers to them as an in-app notification in the Whish app. This step happens entirely on the hosted page; your integration never sends or verifies the OTP.
  3. 3
    Receive the callback. When the payment settles, Whish calls your successCallbackUrl or failureCallbackUrl by GET.
  4. 4
    Confirm before fulfilling. Call Get payment status to verify the outcome, then release the order. Poll it as a fallback if no callback arrived.
  5. 5
    Refund if needed. Use Refund a payment with the original externalId.

A success callback settles the payment; a failure callback does not. successCallbackUrl is your confirmation that the order is paid. failureCallbackUrl only tells you an attempt failed, and the customer can still pay the same link, so confirm with Get payment status before acting on it. See Callbacks.

Authentication & headers

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

HeaderDescription
channelValue provided by Whish.
secretValue provided by Whish.
websiteUrlSite URL or app name. Use the exact value Whish issued with your keys.
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; read code (and any dialog message) to see why.

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.

Get account balance

Returns the real balance of the account. Pass currency as a query-string parameter on the request URL to choose which balance to return.

GET/payment/account/balance?currency={currency}
ParameterTypeRequiredDescription
currencyStringRequiredCurrency 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.

Example request · cURL
cURL · Sandbox
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)'
Response · 200 success
application/json (USD)
{
  "status": true,
  "code": null,
  "dialog": null,
  "actions": null,
  "extra": null,
  "data": {
    "balance": 10439.0
  }
}

Create a payment

Returns the hosted URL where the client is redirected to pay the merchant through their Whish balance.

POST/payment/whish
Example request · cURL
cURL · Sandbox
curl -X POST 'https://partner.api.sbx.whish.money/itel-service/api/payment/whish' \
  -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": "1",
  "currency": "USD",
  "invoice": "Order #1",
  "externalId": "1",
  "successCallbackUrl": "https://merchant.example/api/payment/success",
  "failureCallbackUrl": "https://merchant.example/api/payment/failure",
  "successRedirectUrl": "https://merchant.example/thank-you",
  "failureRedirectUrl": "https://merchant.example/payment-error"
}'

Request body

FieldTypeRequiredDescription
amountStringRequiredAmount to be paid, sent as a JSON string (double-quoted). USD allows up to 2 decimals (minimum 1.00); LBP takes no decimals (minimum 1000).
currencyStringRequiredCurrency: USD or LBP.
invoiceStringOptionalDescription/details of the payment.
externalIdStringRequiredUnique per request; the transaction reference.
successCallbackUrlStringRequiredGET callback URL Whish calls when a payment succeeds. This is your primary confirmation signal, so it is required.
failureCallbackUrlStringRequiredGET callback URL Whish calls when a payment attempt fails. The link stays payable, so this is not a settled outcome.
successRedirectUrlStringRequiredWhere the payer's browser is sent after a successful payment.
failureRedirectUrlStringRequiredWhere the payer's browser is sent after a failed payment.
Request
Request body
{
  "amount": "1",
  "currency": "USD",
  "invoice": "Order #1",
  "externalId": "1",
  "successCallbackUrl": "https://merchant.example/api/payment/success",
  "failureCallbackUrl": "https://merchant.example/api/payment/failure",
  "successRedirectUrl": "https://merchant.example/thank-you",
  "failureRedirectUrl": "https://merchant.example/payment-error"
}
Response · 200 success
application/json
{
  "status": true,
  "code": null,
  "dialog": null,
  "extra": null,
  "data": {
    "collectUrl": "https://whish.money/pay/8nQS2mL"
  }
}

Response fields

FieldTypeDescription
collectUrlStringHosted Whish payment page URL. Redirect the payer here to complete payment from their Whish balance.

Callback and redirect URLs must be publicly reachable. Sending localhost (or any loopback or non-public host) in any of the four callback and redirect URLs above is not allowed: the request is rejected with HTTP 403 Forbidden. Use a public URL.

status: true here means the request succeeded and a collectUrl was generated. It does not mean the customer has paid. The payment outcome is separate: it is delivered to your callback URLs and can be checked with Get payment status.

Whish calls the callback URLs via GET on each payment attempt: successCallbackUrl when the link is paid, failureCallbackUrl when an attempt fails and the link stays open. Before you fulfil the order, confirm the outcome with Get payment status (the callback is an unauthenticated GET, so verify rather than trust it blindly). The redirect URLs only send the client's browser onward. You may append custom query params (order reference, timestamp) to your callback URLs, and Whish forwards them unchanged.

In sandbox the payer receives no OTP in the Whish app, so the hosted page is driven with fixed test values instead. See Sandbox scenarios.

Get payment status

Returns the collect status of a Whish Pay transaction. Use it to reconcile when a callback did not arrive, and to tell a settled outcome from an open one: a failure callback leaves the status pending while the link is still payable, so this endpoint is what confirms a payment finally failed.

POST/payment/collect/status
Example request · cURL
cURL · Sandbox
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": "1" }'

Request body

FieldTypeRequiredDescription
currencyStringRequiredCurrency: USD or LBP.
externalIdStringRequiredThe externalId you sent when creating the payment.
Request
Request body
{
  "currency": "USD",
  "externalId": "1"
}
Response · 200 success
application/json
{
  "status": true,
  "code": null,
  "dialog": null,
  "extra": null,
  "data": {
    "collectStatus": "success",
    "payerPhoneNumber": "96170123456"
  }
}

Response fields

FieldTypeDescription
collectStatusStringPayment status. pending: the link is still payable, including after a failed attempt, so this is not a failure. success: paid, and the link is now expired. failed: the link expired without being paid. refunded: a successful payment was refunded. unknown: the final state could not be determined. Only success and failed are settled outcomes.
payerPhoneNumberStringPhone number that performed the payment.

Refund a payment

Initiates a refund for a Whish Pay transaction.

Requires IP whitelisting. The Refund endpoint only accepts requests from source IPs that Whish has registered for your account. Send Whish the public egress IP(s) your servers call from (Sandbox and Production) to be allowlisted; otherwise refund requests are rejected. See IP whitelisting.

POST/payment/whish/refund
Example request · cURL
cURL · Sandbox
curl -X POST 'https://partner.api.sbx.whish.money/itel-service/api/payment/whish/refund' \
  -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": "1", "refundReason": "Customer canceled order" }'

Request body

FieldTypeRequiredDescription
currencyStringRequiredCurrency: USD or LBP.
externalIdStringRequiredThe externalId of the original payment to refund.
refundReasonStringOptionalReason for the refund.
Request
Request body
{
  "currency": "USD",
  "externalId": "1",
  "refundReason": "Customer canceled order"
}
Response · 200 success
application/json
{
  "status": true,
  "code": null,
  "dialog": null,
  "actions": null,
  "extra": null,
  "retrieved": false,
  "data": null
}
Already processed (duplicate externalId)
application/json
{
  "status": true,
  "code": null,
  "dialog": null,
  "actions": null,
  "extra": null,
  "retrieved": true,
  "data": null
}
Response failure
application/json
{
  "status": false,
  "code": "external_id.not_exists",
  "dialog": null,
  "actions": null,
  "extra": null,
  "data": null
}

Response fields

FieldTypeDescription
retrievedBooleantrue if this refund was already processed before (duplicate).

Callbacks

Callbacks tell you the outcome of a payment attempt. Provide successCallbackUrl and failureCallbackUrl when you create the payment; Whish calls them when an attempt on the payment link succeeds or fails.

A failure callback is not the end of the payment. The payment link stays payable until it is either paid successfully or expires, so a customer whose attempt failed can simply try again on the same link. That is why Get payment status keeps reporting pending after a failure callback: the payment can still succeed. A successful payment expires the link immediately and moves the status to success; only a link 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.

  • Each callback is an HTTP GET with no request body.
  • You get one callback per attempt, so a link paid on the second try produces a failure callback followed by a success callback.
  • Whish adds no identifying parameters of its own, so include your own reference in the callback URL (for example ?externalId=1&order=A-1234); Whish preserves and forwards them unchanged.
  • Because the callback is an unauthenticated GET, treat it as a signal to verify, not as proof. Confirm the outcome with Get payment status before releasing the order.
  • Respond with HTTP 200 to acknowledge receipt.
  • A missing callback (for example a transient network failure) should be reconciled by polling Get payment status, not treated as a failure.
Success callback URL
GET
https://merchant.example/api/payment/success?externalId=1&order=A-1234

Whish calls your callback URLs from a fixed set of source IPs. If your callback endpoint restricts inbound traffic, allowlist them; see IP whitelisting for the current list.

IP whitelisting

IP allowlisting applies to Whish Pay in two directions. Set up whichever your integration uses.

Calling restricted endpoints (you → Whish)

The Refund endpoint only accepts requests from source IPs that Whish has registered for your account. Send Whish the public egress IP address(es) your servers call from, for both Sandbox and Production, and Whish allowlists them on their side. Until then, refund requests from unregistered IPs are rejected.

Receiving callbacks (Whish → you)

Whish calls your successCallbackUrl and failureCallbackUrl from a fixed set of source IPs. If your callback endpoint restricts inbound traffic, allowlist them so callbacks are not blocked. The current list is maintained in one place: Callback source IPs in the Reliability & Idempotency guide.

Sandbox scenarios

Sandbox does not deliver the OTP that the payer would normally receive in the Whish app. Drive the hosted payment page with the fixed values below instead.

Field on the hosted pageTest value
Phone96170123456
OTP111111
ScenarioHow to triggerExpected outcome
Successful paymentPay with the test phone and OTP above.collectStatus becomes success.
Failed attemptEnter any OTP other than 111111.The failure callback fires, and collectStatus stays pending because the link is still payable. Retry on the same link to see it then succeed.
ExpiryLeave the link unpaid until it expires.collectStatus settles as failed.
RefundRefund a payment that succeeded and has not been refunded yet.The refund is accepted. A payment that never succeeded, or was already refunded, cannot be refunded.

Sandbox behaves exactly like production here: a failed attempt does not end the payment, and the status only settles on a successful payment or on expiry. See Callbacks before you code against a failure callback.

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

Whish Pay codes

CodeMeaningRecommended handling
currency.not_supportedOnly LBP and USD are accepted on this route.Send a supported currency.
collect.wrong_denominationAccount has no collect / Whish Pay denomination configured.Contact Whish (account setup).
collect.wrong_phone_formatThe customer phone number could not be normalized.Fix the phone number format (E.164).
collect.wrong_email_formatThe email field is present but invalid.Fix or omit the email.
refresh.denominationsService denomination not available or configured server-side.Retry later; contact Whish if persistent.
credit_card_payment.account_verification_requiredMerchant account is not KYC or financially verified for this operation.Complete account verification with Whish.
whish_pay.missing_pricing_idNo pricing configured for the merchant's Whish Pay profile.Contact Whish.
whish_pay.code_generation_failedPayment-code generation for the payment URL failed.Retry; escalate if persistent.
purchase.confirmThe quoted price changed since the request was prepared (new price in extra.price).Re-quote and resubmit with the new price.
failPricing or amount computation failed (reason in the dialog).Check the dialog message; verify amount and currency.
external_id.not_existsThe externalId given to Refund does not exist.Refund only an externalId from a real payment.
itel.unknown_errorUnhandled exception.Retry with backoff.

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.4.42026-07-15Documentation: error-code reference added; externalId documented as a String; localhost callback/redirect URLs rejected with HTTP 403; sandbox base URL updated.
v1.4.32026-02-11Refund API added (POST /payment/whish/refund).
v1.4.22026-02-09Get Rate removed with minor adjustment.
v1.4.12026-02-03Get Balance now supports LBP and USD.
v1.42025-12-23User-Agent requirement and additional field descriptions and notes.
v1.3.12025-12-11Production base URL update.
v1.32025-10-03Sandbox base URL update.
v1.22025-09-17Get Status returns payerPhoneNumber in the response.
v1.12025-05-12Test cases and request content-type requirements added.
v1.02022-11-08Initial release.

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.