Whish Pay Split
Collect a payment and record how it should be split across destination accounts, by amount or by percentage.
REST over HTTPS with JSON requests and responses. Intended for authorized partners.
The contract on this page is final; fund redistribution is not live yet. The endpoint, request fields, and response shape below are settled and safe to integrate against now. Actual redistribution of funds to each breakdown identifier, including fee handling, is still being finalized on the Whish side. Until it ships, breakdown and breakdownType are captured and held against the payment, and the full amount is collected from the payer as normal. Whish will confirm when redistribution goes live; no change to your integration is expected at that point.
Integration flow
- 1Create the split payment. Call Create a split payment with the amount, currency, your
externalId, thebreakdown, and your callback URLs. You receive acollectUrl. - 2Redirect 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. - 3Receive the callback. When the payment settles, Whish calls your
successCallbackUrlorfailureCallbackUrlby GET. - 4Confirm before fulfilling. Call Get payment status to verify the outcome, then release the order. Poll it as a fallback if no callback arrived.
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.
| Header | Description |
|---|---|
channel | Value provided by Whish. |
secret | Value provided by Whish. |
websiteUrl | Site URL or app 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.
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 payment's progress. |
dialog | Object | Optional title / message to display to the user. |
data | Object | Response payload for the operation; shape varies per endpoint. |
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 with Get payment status; do not mark it failed. |
false | any other code | Failed; 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 payment has progressed. The payment's own lifecycle is reported on data.collectStatus by Get payment status.
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.
| 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
}
}Response fields
| Field | Type | Description |
|---|---|---|
balance | Double | Account balance in the requested currency. |
Create a split payment
Creates a payment and records how the collected amount should be split across destination accounts. Returns the hosted URL where the payer completes the payment from their Whish balance.
curl -X POST 'https://partner.api.sbx.whish.money/itel-service/api/payment/whish/split' \
-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": 100,
"currency": "USD",
"externalId": "your-unique-id-001",
"invoice": "Order #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",
"breakdownType": "amount",
"breakdown": {
"destination-1": 60,
"destination-2": 40
}
}'Request body
| Field | Type | Required | Description |
|---|---|---|---|
amount | Number | Required | Amount to collect from the payer. Send it as a JSON number; a double-quoted string such as "100" is also accepted. When breakdownType is amount, this should equal the sum of the breakdown values. |
currency | String | Required | Currency: USD or LBP. |
externalId | String or Number | Required | Your unique idempotency key for this payment, and the transaction reference. Re-sending a value already used replays the original result instead of creating a second payment. |
breakdown | Object | Required | Map of identifier: value describing how the payment should be split. See The breakdown object. |
breakdownType | String | Optional | How to read the breakdown values: amount (default) or percentage. See The breakdown object. |
invoice | String | Optional | Free-text reference shown on the payment page. |
successCallbackUrl | String | Required | GET callback URL Whish calls when a payment succeeds. This is your primary confirmation signal, so it is required. |
failureCallbackUrl | String | Required | GET callback URL Whish calls when a payment attempt fails. The link stays payable, so this is not a settled outcome. |
successRedirectUrl | String | Required | Where the payer's browser is sent after a successful payment. |
failureRedirectUrl | String | Required | Where the payer's browser is sent after a failed payment. |
{
"amount": 100,
"currency": "USD",
"externalId": "your-unique-id-001",
"invoice": "Order #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",
"breakdownType": "amount",
"breakdown": {
"destination-1": 60,
"destination-2": 40
}
}{
"status": true,
"code": null,
"data": {
"collectUrl": "https://pay.whish.money/invoice/pay/?q=abc123"
}
}Response fields
| Field | Type | Description |
|---|---|---|
data.collectUrl | String | Hosted Whish payment page URL. Send the payer here to complete the 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.
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 status of a payment. Call it with the externalId you sent when creating the payment. 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.
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": "your-unique-id-001" }'Request body
| Field | Type | Required | Description |
|---|---|---|---|
currency | String | Required | Currency: USD or LBP. |
externalId | String or Number | Required | The externalId you sent when creating the split payment. |
{
"currency": "USD",
"externalId": "your-unique-id-001"
}{
"status": true,
"code": null,
"dialog": null,
"extra": null,
"data": {
"collectStatus": "success",
"payerPhoneNumber": "96170123456"
}
}Response fields
| Field | Type | Description |
|---|---|---|
collectStatus | String | Payment 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. |
payerPhoneNumber | String | Phone number that performed the payment. |
The status reports the collect from the payer, which is the whole amount. It does not report per-destination distribution: redistribution to each breakdown identifier is not live yet. See the note at the top of this page.
The breakdown object
breakdown is an object of identifier: value pairs. The identifier is any string you choose to label a destination: a name, a phone number, or an account id. Several identifiers may point at the same underlying destination. How the value is read depends on breakdownType.
breakdownType | How breakdown values are read | Expected total |
|---|---|---|
amount (default) | Each value is a literal amount in the collect's currency. | The values should sum to amount. |
percentage | Each value is a percentage (0-100) of the payment. | The values should sum to 100. |
Amount mode (default)
Use this when you already know the exact figure each destination should receive. Omitting breakdownType selects it.
"breakdownType": "amount",
"breakdown": {
"destination-1": 60,
"destination-2": 40
}With breakdownType: "amount", amount should equal the sum of the breakdown values. In the example above, amount is 100.
Percentage mode
Use this when you do not know each destination's exact figure up front, for example because a fee is deducted on the merchant's side and you would rather split whatever actually lands than split what you predicted. Set breakdownType to percentage and send the values as percentages instead of amounts.
"breakdownType": "percentage",
"breakdown": {
"destination-1": 60,
"destination-2": 40
}Percentages should sum to 100 across the breakdown. The payer is still charged amount; the percentages describe how the collected payment is divided, not what is collected.
Callbacks
Callbacks tell you the outcome of a payment attempt. Provide successCallbackUrl and failureCallbackUrl when you create the split 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=your-unique-id-001&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.
- The callback carries no breakdown information: it reports the outcome of the collect from the payer.
https://merchant.example/api/payment/success?externalId=your-unique-id-001&order=A-1234Whish 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
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 page | Test value |
|---|---|
| Phone | 96170123456 |
| OTP | 111111 |
| Scenario | How to trigger | Expected outcome |
|---|---|---|
| Successful payment | Pay with the test phone and OTP above. | collectStatus becomes success. |
| Failed attempt | Enter 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. |
| Expiry | Leave the link unpaid until it expires. | collectStatus settles as failed. |
| Amount breakdown | Send breakdownType: "amount" with values summing to amount. | The payment is created and the breakdown is recorded against it. |
| Percentage breakdown | Send breakdownType: "percentage" with values summing to 100. | The payment is created and the breakdown is recorded against it. |
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. Sandbox does not redistribute funds to the breakdown identifiers, because that step is not live in either environment yet.
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 payment: the recorded result is replayed verbatim, including the original error code (safe retry, no double charge).
{
"status": false,
"code": "400",
"dialog": {
"title": "Error",
"message": "Invalid request"
},
"actions": null,
"extra": null,
"data": null
}Payment codes
| Code | Meaning | Recommended handling |
|---|---|---|
currency.not_supported | Only LBP and USD are accepted on this route. | Send a supported currency. |
collect.wrong_denomination | The account has no collect denomination configured. | Contact Whish (account setup). |
collect.wrong_phone_format | The customer phone number could not be normalized. | Fix the phone number format (E.164). |
collect.wrong_email_format | An email address supplied for the payment failed validation. | Correct the address, or omit it. |
refresh.denominations | Service denomination not available or configured server-side. | Retry later; contact Whish if persistent. |
credit_card_payment.account_verification_required | Merchant account is not KYC or financially verified for this operation. | Complete account verification with Whish. |
whish_pay.missing_pricing_id | No pricing configured for the merchant's Whish Pay profile. | Contact Whish. |
whish_pay.code_generation_failed | Payment-code generation for the payment URL failed. | Retry; escalate if persistent. |
external_id.already_used | The externalId is linked to another amount or another session. | Use a new externalId for a genuinely new payment. |
purchase.confirm | The quoted price changed since the request was prepared (new price in extra.price). | Re-quote and resubmit with the new price. |
fail | Pricing or amount computation failed (reason in the dialog). | Check the dialog message; verify amount and currency. |
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. A malformed breakdown is rejected here. | 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:
| Code | Meaning | Recommended handling |
|---|---|---|
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. |
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.0.0 | 2026-08-20 | Initial release of Whish Pay Split (POST /payment/whish/split) with breakdown and breakdownType. Request and response contract final; fund redistribution to each breakdown identifier is still being finalized. |
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.