International Money Transfer
Programmatically send money, quote fees, track transfers, and reconcile statements through the Whish Money network.
This reference describes the HTTP request and response models used to integrate with Whish Money, and the operations available to move funds across the network.
Integration flow
- 1Quote the fee. Call Get transfer fees to show the customer the total cost before you commit.
- 2Create the transfer. Call Create a transfer with the recipient details, amount, and a unique
Request-id. SetpaymentModeto choose how the funds are delivered: cash collection or mobile wallet. - 3Track the outcome by mode. Cash collection issues an
ltnNumber(statusSENT) that the receiver collects at a Whish Money agent; collection moves it toRECEIVEDand triggers your callback. Mobile wallet credits the receiver's wallet directly and is final in the Create a transfer response, with no callback. - 4Confirm and reconcile. Use Get transfer status to verify the outcome; for cash collection it is also your fallback if a callback did not arrive.
- 5Cancel if needed. A cash-collection transfer can be reversed with Cancel a transfer while the funds have not yet been collected. Mobile wallet transfers are final and cannot be canceled.
Authentication & headers
Every request is authenticated with two credentials issued by Whish Money, sent as HTTP headers. Unless an endpoint states otherwise, all requests must include the headers below, plus Content-Type: application/json on requests that send a body.
| Header | Value | Description |
|---|---|---|
username | - | Account username, provided by Whish Money. |
key | - | Secret API key, provided by Whish Money. |
Content-Type | application/json | Required for requests that send a JSON body. |
Request-id | - | Required on every request. Unique numeric request identifier generated by your account (the API accepts numeric values only). See the note below. |
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.
Send Request-id on every request, and mint a new one for every new Create a Transfer. It must be numeric (Long), so use epoch milliseconds plus a counter, or your own numeric transaction reference. Store it before you send.
Reuse it on purpose to recover from a timeout. Resend the identical request with the same Request-id: if it never reached us the transfer is created, and if it did, nothing is duplicated and you get transfer.already.done. Then call Get Transfer Status with that requestId to read the outcome. Reusing an id for a genuinely new transfer hits the same guard, and that transfer is never created.
Environments
Whish Money provides two isolated environments. Validate your full integration in Sandbox before requesting Production access.
| Environment | Base URL | Purpose |
|---|---|---|
| Sandbox | https://sandbox.woocash.money/api | Testing, QA, and integration verification. |
| Production | https://api.woocash.money | Live environment for real users and transactions. |
The cURL examples on this page target Sandbox. To go live, swap the host for the Production base URL above.
Building a request URL
Each endpoint path in this reference begins with /api/woo/…. Append the endpoint path to the environment base URL. Note that the Sandbox base URL already ends in /api, so the full Sandbox URL contains /api/api/….
For the example endpoint POST /api/woo/send/money:
| Environment | Full URL |
|---|---|
| Sandbox | https://sandbox.woocash.money/api/api/woo/send/money |
| Production | https://api.woocash.money/api/woo/send/money |
The two environments use different base-URL conventions. Sandbox includes a trailing /api segment while Production does not. Construct URLs by concatenating the base URL and the endpoint path exactly as shown above, rather than assuming both environments share the same prefix. Endpoint sections below list the path only.
Response format
All endpoints return HTTP 200 with Content-Type: application/json and a shared envelope. Always branch on the status field in the body, not on the HTTP status code.
| Field | Type | Description |
|---|---|---|
status | Boolean | true = success · false = failure. |
code | String | Error code when status = false; null on success. See Error codes. |
data | Object | Array | Payload of the operation on success; null on failure. Shape varies per endpoint: an object for most, an array for List payment modes. |
dialog | Object | Optional user-friendly message (title / message) for display. Usually null on success, but some successful responses (for example a Mobile Wallet top-up) include an informational dialog. |
dialog.title | String | Title of the message (e.g. "Sorry!"). |
dialog.message | String | Human-readable description of the outcome, for display to the end user. Usually an error, but some successful responses carry an informational message. |
extra | Object | Reserved for additional context; typically null. |
{
"status": true,
"code": null,
"dialog": null,
"extra": null,
"data": { }
}{
"status": false,
"code": "api.account.invalid_request",
"dialog": {
"title": "Sorry!",
"message": "api.account.invalid_request"
},
"extra": null,
"data": null
}Recommended handling: read status first. On true, process data. On false, switch on code for programmatic logic and surface dialog.message to end users. Specialized rules apply to Get Transfer Status and the Create a Transfer / Mobile Wallet responses.
The envelope status is a Boolean and answers only whether the call worked. It is a different field from data.status, the String that carries the transfer's own progress (SENT, RECEIVED, CANCELED_FULL). A completed call reporting a transfer that has not arrived yet is status: true with data.status: "SENT".
Payment modes
Every transfer runs in one of two modes, set by paymentMode on Create a Transfer. They behave differently end to end, so decide which one you integrate before you build.
| Aspect | Cash Collection | Mobile Wallet |
|---|---|---|
paymentMode | CASH_COLLECTION | MOBILE_WALLET |
| How funds arrive | Receiver collects cash from a Whish Money agent using the ltnNumber. | Funds are credited directly to the receiver's Whish wallet. |
| Outcome timing | Moves through statuses over time (SENT → RECEIVED). | Instant and final in the Create a Transfer response. |
| Callback | Yes, on SENT → RECEIVED (set callbackUrl). See Callbacks. | None. Nothing to notify later. |
Pending (code: 500) | Escalate to Whish Money to determine the final outcome; treat as Pending meanwhile. See Transaction handling. | Same: escalate to Whish Money and treat as Pending. See Transaction handling. |
| Cancellable | Yes, before collection. See Cancel a Transfer. | No. The outcome is already final. |
Transaction status & handling
Interpret every transfer outcome using the rule below. It applies to both payment modes and lets you resolve most cases automatically, without escalating to Whish Money.
Transaction status interpretation
| Outcome | Condition | How to treat it |
|---|---|---|
| Successful | status: true | Consider the transaction successful. |
| Failed | status: false and code is not 500 | Consider it failed. Handle it automatically based on the returned error code. No escalation to Whish Money is required. |
| Pending | status: false and code is 500 (Internal Server Error) | Consider it pending. Escalate and follow up with Whish Money to determine the final outcome. |
Receipt issuance
Issue a receipt to the customer only in these cases:
| Case | Issue receipt? |
|---|---|
Successful (status: true) | Yes |
Pending (status: false, code: 500) | Yes (pending investigation) |
| Failed (business-validation error) | No |
Do not issue receipts for failed transactions. Any transaction returned as failed because of one of the final-failure error codes below must not produce a customer receipt.
Handling timeouts & network issues
If you do not receive a response from POST /api/woo/send/money because of a timeout, network interruption, or any communication issue, resend the same request using the exact same Request-id. This is safe:
- If the original request never reached Whish Money, the transfer is initiated now.
- If the original request was already received, no duplicate is created and the resend answers
transfer.already.done. That is a confirmation, not a failure: it tells you the transfer exists. It does not replay the original payload, so call Get Transfer Status with thatrequestIdto read the outcome.
Resend before you check status. Confirm the original POST /api/woo/send/money was received before relying on Get Transfer Status. If you query the status of a transfer that was never received, the status endpoint returns sending.transfer.invalid.info, which means the referenced transaction does not exist in Whish Money's records (most likely the original request never arrived). Resend the original request with the same Request-id first, then check status only after the transfer is registered.
Error codes to treat as final failures
Automate the following as failed transactions and resolve them on your side. None of these require contacting Whish Money.
| Error code | Description |
|---|---|
api.account.invalid_request | Invalid account request. |
receiver.empty | Receiver information is missing. |
receiver.invalid.phone_number | Receiver phone number is invalid. |
receiver.customer.invalid_name | Receiver name is invalid. |
receiver.customer.info_invalid | Receiver information is invalid. |
receiver.customer.name_enlgish | Receiver name must be in English. |
sender.invalid.phone_number | Sender phone number is invalid. |
sender.customer.invalid_name | Sender name is invalid. |
sender.customer.info_invalid | Sender information is invalid. |
sender.customer.name_enlgish | Sender name must be in English. |
transfer.already.done | This Request-id was already used, so the transfer was not created again. On a resend after a timeout this is the expected, safe answer: call Get Transfer Status with that requestId to read the outcome. |
sender.missing_info | Sender information is missing. |
currency.not_supported | Currency not supported. |
transfer.invalid_amount | Amount is negative or sending amount not found in price list. |
transfer.reason_empty | Reason not provided in the request. |
transfer.from_country_not_allowed | Transfers from this country are not allowed. |
transfer.sourceOfFunds_empty | Source of funds not provided in the request. |
transfer.not_allowed_countries | Transfer to these countries is not allowed. |
transfer.account_reach_balance_limit | Reached balance limit or insufficient funds. |
transfer.exceeds_account_transaction_limit | Sending amount exceeds the allowed transaction limit. |
service.inactive | Service is inactive. |
service.price_not_active | Service price is not active. |
transfer.miss_config | Transfer configuration is missing. |
service.transaction_status_unknown | Transaction status is unknown. |
service.transaction_failed | Transaction failed. |
sender.invalid.date.of.birth | Invalid date of birth. |
transfer.exceeds_account_daily_limit | Reached daily sending limit. |
receiver.whish.account.not.exists | Whish account does not exist (Mobile Wallet mode). |
sender.kyc.needed | Sender's KYC is required. |
receiver.kyc.needed | Receiver's KYC is required. |
sender.interview.needed | Additional information required for the sender. |
receiver.interview.needed | Additional information required for the receiver. |
The code: 500 pending case is the only outcome that warrants follow-up with Whish Money.
Shared models
Objects reused by the request bodies above.
Customer model (sender / receiver)
Represents a party to the transfer. Carries the basic information needed to identify a sender or receiver.
| Field | Type | Required | Description |
|---|---|---|---|
firstName | String | Required | First name. |
middleName | String | Optional | Middle name. |
lastName | String | Required | Last name. |
phone | String | Required | Phone number. |
extraInformation | String | Optional | Additional information about the customer. |
identity | Identity | Conditional | Identity details about the customer. Required for the receiver when paymentMode is CASH_COLLECTION, which is the mode that requires dateBirth. Optional for the sender in both payment modes. See Date of birth requirements. |
"receiver": {
"firstName": "Alex",
"middleName": "something",
"lastName": "hunter",
"phone": "96170123456",
"extraInformation": "useful info",
"identity": {
"dateBirth": "01-01-1990",
"nationality": "lebanese"
}
}Identity model
Provides additional detail about a customer's identity.
| Field | Type | Required | Description |
|---|---|---|---|
nationality | String | Optional | Nationality. |
dateBirth | String | Conditional | Date of birth. Format: dd-MM-yyyy. Whether it is required depends on the party and the paymentMode: see Date of birth requirements. |
"identity": {
"dateBirth": "01-01-1990",
"nationality": "lebanese"
}Date of birth requirements
identity.dateBirth uses the format dd-MM-yyyy. Whether you must send it depends on the party and on paymentMode:
| Party | CASH_COLLECTION | MOBILE_WALLET |
|---|---|---|
Sendersender.identity.dateBirth | Optional | Optional |
Receiverreceiver.identity.dateBirth | Required | Optional |
Because receiver.identity.dateBirth is required for CASH_COLLECTION, a Cash Collection transfer must carry a receiver.identity object. sender.identity is optional in both payment modes: send it only when you hold the data.
Get transfer fees
Retrieve the fees for an amount to be sent, given the amount, currency, and payment mode.
curl -X POST 'https://sandbox.woocash.money/api/api/woo/send/money/fees' \
-H 'username: YOUR_USERNAME' \
-H 'key: YOUR_KEY' \
-H 'Request-id: 1720598400123' \
-H 'Content-Type: application/json' \
-d '{
"fromCountry": 1,
"toCountry": 1,
"currencyId": 2,
"amount": 100,
"deductFees": false,
"paymentMode": "CASH_COLLECTION",
"phoneNumber": "96170123456"
}'Request body
| Field | Type | Required | Description |
|---|---|---|---|
fromCountry | Long | Required | Sending country id. Read the ids available to you from List sending countries rather than hard-coding them. |
toCountry | Long | Required | Receiving country id. Always Lebanon (1): this API delivers funds in Lebanon, so only the sending country varies. |
currencyId | Long | Required | Currency id (USD = 2, LBP = 1). |
paymentMode | String | Optional | Enum: CASH_COLLECTION, MOBILE_WALLET. Defaults to CASH_COLLECTION when omitted. |
amount | double | Required | Sending amount in the chosen currency. |
deductFees | Boolean | Optional | If true, fees are deducted from the sending amount, so the receiver collects less than amount. If false, fees are charged on top of amount. Defaults to false when omitted. |
phoneNumber | String | Optional | Recipient phone number the fees are quoted for. |
{
"fromCountry": 1,
"toCountry": 1,
"currencyId": 2,
"amount": 100,
"deductFees": false,
"paymentMode": "CASH_COLLECTION",
"phoneNumber": "96170123456"
}Response fields (in data)
The cost the user will pay to send the amount.
Field (in data) | Type | Description |
|---|---|---|
fees | Double | Fees in the sending currency. |
amount | Double | Amount to be sent. |
totalAmount | Double | Sum of amount and fees. |
{
"status": true,
"code": null,
"dialog": null,
"extra": null,
"data": {
"fees": 1,
"amount": 100,
"totalAmount": 101
}
}{
"status": false,
"code": "transfer.invalid_amount",
"dialog": {
"title": "Sorry!",
"message": "Transfer Amount Outside Range"
},
"extra": null,
"data": null
}Create a transfer
Create a transfer through Whish Money, specifying the amount, currency, countries, and parties.
curl -X POST 'https://sandbox.woocash.money/api/api/woo/send/money' \
-H 'username: YOUR_USERNAME' \
-H 'key: YOUR_KEY' \
-H 'Request-id: 1720598400123' \
-H 'Content-Type: application/json' \
-d '{
"paymentMode": "CASH_COLLECTION",
"fromCountry": 1,
"toCountry": 1,
"currencyId": 2,
"amount": 100,
"sender": {
"firstName": "personName",
"lastName": "LastName",
"phone": "96170654321"
},
"receiver": {
"firstName": "personName",
"lastName": "LastName",
"phone": "96170123456",
"identity": { "dateBirth": "01-01-1990", "nationality": "lebanese" }
},
"reason": "Donations & Gifts",
"sourceOfFunds": "Income from Own Business",
"callbackUrl": "https://merchant.example/whish/callback"
}'Request-id must be unique for each transfer. See Authentication & headers.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
paymentMode | String | Optional | Enum: CASH_COLLECTION, MOBILE_WALLET. Defaults to CASH_COLLECTION when omitted. |
fromCountry | Long | Required | Sending country id. Read the ids available to you from List sending countries rather than hard-coding them. |
toCountry | Long | Required | Receiving country id. Always Lebanon (1): this API delivers funds in Lebanon, so only the sending country varies. |
currencyId | Long | Required | Currency id (USD = 2, LBP = 1). |
amount | double | Required | Sending amount in the chosen currency. |
sender | Customer | Required | Information about the sender. |
receiver | Customer | Required | Information about the receiver. |
deductFees | Boolean | Optional | If true, fees are deducted from the sending amount, so the receiver collects less than amount. If false, fees are charged on top of amount. Defaults to false when omitted. |
reason | String | Required | Reason for the transfer. See Reason values. |
sourceOfFunds | String | Required | Source of funds (e.g. Salary, Sale of Property or Goods). See Source of Funds. |
callbackUrl | String | Optional | Cash Collection only. URL Whish Money calls when the transaction changes from SENT to RECEIVED. Not used for Mobile Wallet. See Callbacks. |
Both sender and receiver are required. Each is a Customer object; a request missing either party is rejected. For CASH_COLLECTION, receiver.identity.dateBirth is required as well; sender.identity is optional in both payment modes. See Date of birth requirements.
{
"paymentMode": "CASH_COLLECTION",
"fromCountry": 1,
"toCountry": 1,
"currencyId": 2,
"amount": 100,
"sender": {
"firstName": "personName",
"middleName": "m",
"lastName": "LastName",
"phone": "96170654321",
"extraInformation": "extraInformation",
"identity": {
"dateBirth": "01-01-1990",
"nationality": "lebanese"
}
},
"receiver": {
"firstName": "personName",
"middleName": "m",
"lastName": "LastName",
"phone": "96170123456",
"extraInformation": "extraInformation",
"identity": {
"dateBirth": "01-01-1990",
"nationality": "lebanese"
}
},
"deductFees": false,
"reason": "Donations & Gifts",
"sourceOfFunds": "Income from Own Business",
"callbackUrl": "https://merchant.example/whish/callback"
}Response fields (in data)
Returned when the transfer was successfully created.
Field (in data) | Type | Description |
|---|---|---|
ltnNumber | String | In Cash Collection, the LTN number the receiver uses to collect the transfer from Whish Money cash agents. In Mobile Wallet it is returned as a reference only: funds are credited directly, so there is nothing to collect with it. |
transferId | String | Reference of the transfer. |
charges | Double | Fees applied to the transfer. |
Mobile Wallet response interpretation
| Case | Condition | Treat as |
|---|---|---|
| Success | status = true | Success |
| Pending | status = false and code = 500 | Pending |
| Failure | status = false and code ≠ 500 | Failure |
The value 500 is the Pending signal in both payment modes: on status: false, compare code against 500 before treating the send as failed. Every other code is a string error identifier and, per the rule above, a final failure.
{
"status": true,
"code": null,
"dialog": null,
"extra": null,
"data": {
"ltnNumber": "9262-6531-4941",
"transferId": "2020125708",
"charges": 1.0
}
}{
"status": false,
"code": "transfer.reason_empty",
"dialog": {
"title": "Sorry!",
"message": "Transfer Reason is Missing"
},
"extra": null,
"data": null
}{
"status": true,
"code": null,
"dialog": {
"title": "Success",
"message": "Your Account Has Been Successfully Topup"
},
"extra": null,
"data": {
"ltnNumber": "MW223871662783",
"transferId": "AA 2500000104",
"currency": null,
"charges": 1.0,
"collectionAmount": 100.0,
"balance": 0.0,
"transactionId": 130482
}
}Mobile Wallet response fields (in data)
In addition to ltnNumber, transferId, and charges, a Mobile Wallet send returns:
Field (in data) | Type | Description |
|---|---|---|
collectionAmount | Double | Amount credited to the receiver's wallet, in the receiving currency's minor-free units as returned. |
balance | Double | Your account balance after the send. |
transactionId | Long | Numeric wallet transaction identifier. Distinct from transferId (the transfer reference string). |
currency | String | Currency of the credited amount; may be null. |
Cancel a transfer
Cancel a transfer that has already been sent, by its LTN number.
Cancellation is available for Cash Collection transfers only. Mobile Wallet transfers cannot be canceled via API, because the funds are delivered instantly at send time. A cancel request for a Mobile Wallet transfer is rejected with transfer.cannot_be_canceled.
curl -X POST 'https://sandbox.woocash.money/api/api/woo/cancel/send/money' \
-H 'username: YOUR_USERNAME' \
-H 'key: YOUR_KEY' \
-H 'Request-id: 1720598400123' \
-H 'Content-Type: application/json' \
-d '{ "ltnNumber": "9262-6531-4941" }'Request body
| Field | Type | Required | Description |
|---|---|---|---|
ltnNumber | String | Required | LTN number of the transfer to cancel. |
reason | String | Optional | Reason for cancellation. Recorded on your account activity log for audit; it is not stored against the cancellation itself and is not returned by any endpoint. |
{
"ltnNumber": "9262-6531-4941",
"reason": "Customer changed their mind"
}Response fields (in data)
Field (in data) | Type | Description |
|---|---|---|
ltnNumber | String | LTN number of the transfer. |
transferId | String | Reference of the transfer. |
status | String | Resulting status of the transfer, CANCELED_FULL on success. Note the spelling: this and the callback use CANCELED_FULL, while the same cancellation shows as CANCELLED in List account activity. |
receiverIdentity | Object | Receiver identity details when present; null for a cancellation. |
{
"status": true,
"code": null,
"dialog": null,
"extra": null,
"data": {
"ltnNumber": "9262-6531-4941",
"transferId": "2020125708",
"status": "CANCELED_FULL",
"receiverIdentity": null
}
}{
"status": false,
"code": "transfer.not_exists",
"dialog": {
"title": "Sorry!",
"message": "Transfer Not Found"
},
"extra": null,
"data": null
}Get transfer status
Retrieve the current status of a transfer.
curl -X POST 'https://sandbox.woocash.money/api/api/woo/send/money/status' \
-H 'username: YOUR_USERNAME' \
-H 'key: YOUR_KEY' \
-H 'Request-id: 1720598400123' \
-H 'Content-Type: application/json' \
-d '{ "transferId": "2020125708", "ltnNumber": "9262-6531-4941", "requestId": 1720598400123 }'Request body
| Field | Type | Required | Description |
|---|---|---|---|
transferId | String | Optional | Id of the transfer. |
ltnNumber | String | Optional | LTN number of the transfer. |
requestId | Long | Optional | The Request-id header value you sent with the original Create a Transfer request. You can pass it here to look up any transaction by your own reference, for either payment mode. |
{
"transferId": "2020125708",
"ltnNumber": "9262-6531-4941",
"requestId": 1720598400123
}Response fields (in data)
Field (in data) | Type | Description |
|---|---|---|
ltnNumber | String | LTN number of the transfer. |
transferId | String | Reference of the transfer. |
status | String | Status of the transfer (e.g. SENT, RECEIVED, CANCELED_FULL). |
receiverIdentity | Object | Receiver identity details (see nested fields). |
receiverIdentity.number | String | Identity number. |
receiverIdentity.type | String | Identity type (e.g. Lebanese ID). |
receiverIdentity.frontImage | String | URL of the identity front image. |
receiverIdentity.backImage | String | URL of the identity back image. |
{
"status": true,
"code": null,
"dialog": null,
"extra": null,
"data": {
"ltnNumber": "9262-6531-4941",
"transferId": "2020125708",
"status": "RECEIVED",
"receiverIdentity": {
"number": "1990xxxxxx",
"type": "Lebanese ID",
"frontImage": "https://cdn.woocash.money/id/2020125708-front.jpg",
"backImage": "https://cdn.woocash.money/id/2020125708-back.jpg"
}
}
}receiverIdentity is populated once the receiver has been identified (typically at collection); it may be null at earlier statuses such as SENT.
{
"status": false,
"code": "transfer.not_exists",
"dialog": {
"title": "Sorry!",
"message": "Transfer Not Found"
},
"extra": null,
"data": null
}Response handling
If status = true → treat the request as successful and read data.status for the actual transaction status (e.g. SENT, RECEIVED, CANCELED_FULL).
If status = false → branch on the error code:
• sending.transfer.invalid.info → the referenced transfer does not exist in Whish Money's records. Usually this means the original Create a Transfer request never arrived. Resend it with the same Request-id before checking status again. See Handling timeouts & network issues.
• transfer.not_exists → treat as Failed.
• Any other code → apply the transaction status interpretation (code: 500 is Pending and warrants follow-up; other codes are final failures).
List sending countries
Retrieve the countries your account may send from, and their id values. Pass the one you want to Get transfer fees and Create a transfer as fromCountry. toCountry is always Lebanon, because this API delivers funds in Lebanon.
Take country ids from this endpoint, not from these examples. In production your list is the set agreed with Whish Money for your account; in sandbox only Lebanon is enabled unless you ask for more. Call it again against production when you go live to confirm what your account has.
curl 'https://sandbox.woocash.money/api/api/woo/sending/countries' \
-H 'username: YOUR_USERNAME' \
-H 'key: YOUR_KEY' \
-H 'Request-id: 1720598400123'Request body: None.
Response fields (in data)
Field (in data.list[]) | Type | Description |
|---|---|---|
id | Long | Country id. |
name | String | Country name. |
code | String | ISO country code (e.g. LB). |
defaultCurrencyId | Long | Default currency id for the country, or null. |
dateCreation | Long | Creation timestamp (epoch milliseconds). |
{
"status": true,
"code": null,
"dialog": null,
"extra": null,
"data": {
"list": [
{
"id": 1,
"name": "LEBANON",
"code": "LB",
"defaultCurrencyId": 2,
"dateCreation": 1649173044000
},
{
"id": 2,
"name": "Afghanistan",
"code": "AF",
"defaultCurrencyId": null,
"dateCreation": 1649173050000
}
]
}
}List account activity
Retrieve transfers sent or received within a given period.
curl -X POST 'https://sandbox.woocash.money/api/api/woo/activity' \
-H 'username: YOUR_USERNAME' \
-H 'key: YOUR_KEY' \
-H 'Request-id: 1720598400123' \
-H 'Content-Type: application/json' \
-d '{ "fromDate": "2026-01-01 00:00:00", "toDate": "2040-12-31 23:59:59", "currencyId": 2 }'Request body
| Field | Type | Required | Description |
|---|---|---|---|
fromDate | String | Required | Start date. Format: yyyy-MM-dd HH:mm:ss. |
toDate | String | Required | End date. Format: yyyy-MM-dd HH:mm:ss. |
currencyId | Long | Optional | Currency id (USD = 2, LBP = 1). Send null to include both. |
{
"fromDate": "2026-01-01 00:00:00",
"toDate": "2040-12-31 23:59:59",
"currencyId": 2
}Response fields (in data)
Field (in data.list[]) | Type | Description |
|---|---|---|
id | String | Transfer id. |
status | String | Status of the transfer at this point: SENT, RECEIVED, or CANCELLED. This list is an event log, so a cancelled transfer appears twice, once SENT and again CANCELLED, sharing the same id and requestId. Note that a cancellation reads as CANCELLED here but as CANCELED_FULL on Cancel a transfer and in the callback. |
amount | Double | Amount of the transfer. |
fee | Double | Fee of the transfer. |
total | Double | Sum of amount and fee. The same quantity Get transfer fees returns as totalAmount. |
currency | String | Currency code. |
date | String | Transaction date. Format: yyyy-MM-dd HH:mm:ss. |
requestId | Long | The Request-id you sent when creating the transfer, echoed here so you can match this line to your own record. |
{
"status": true,
"code": null,
"dialog": null,
"extra": null,
"data": {
"list": [
{
"id": "efi 2600000006",
"status": "SENT",
"amount": 100.0,
"fee": 1.0,
"total": 101.0,
"currency": "USD",
"date": "2026-07-21 12:25:14",
"requestId": 1720598400123
},
{
"id": "efi 2600000006",
"status": "CANCELLED",
"amount": 100.0,
"fee": 1.0,
"total": 101.0,
"currency": "USD",
"date": "2026-07-21 12:28:30",
"requestId": 1720598400123
},
{
"id": "efi 2600000007",
"status": "SENT",
"amount": 10.0,
"fee": 0.1,
"total": 10.1,
"currency": "USD",
"date": "2026-07-21 12:30:20",
"requestId": 1720598400124
}
]
}
}{
"status": false,
"code": "transfer.invalid_amount",
"dialog": {
"title": "Sorry!",
"message": "Error"
},
"extra": null,
"data": null
}Get account statement
Retrieve the account statement within a given period, grouped per currency.
curl -X POST 'https://sandbox.woocash.money/api/api/woo/statement' \
-H 'username: YOUR_USERNAME' \
-H 'key: YOUR_KEY' \
-H 'Request-id: 1720598400123' \
-H 'Content-Type: application/json' \
-d '{ "fromDate": "2026-01-01 00:00:00", "toDate": "2040-12-31 23:59:59", "currencyId": 2 }'Request body
| Field | Type | Required | Description |
|---|---|---|---|
fromDate | String | Required | Start date. Format: yyyy-MM-dd HH:mm:ss. |
toDate | String | Required | End date. Format: yyyy-MM-dd HH:mm:ss. |
currencyId | Long | Optional | Currency id (USD = 2, LBP = 1). Send null to include both. |
{
"fromDate": "2026-01-01 00:00:00",
"toDate": "2040-12-31 23:59:59",
"currencyId": 2
}Response fields (in data)
Returns the statement grouped per currency.
| Field | Type | Description |
|---|---|---|
data.list[] | Array | One entry per currency. |
…transactions[] | Array | Transactions for the currency. |
…transactions[].id | String | Transaction id. |
…transactions[].type | String | Transaction type. TRANSFER is a send you made; TOPUP is account funding. Treat this as an open set: other values may appear. |
…transactions[].debit | Double | Amount that raised your balance, for example a TOPUP. 0.0 when the entry did not add funds. |
…transactions[].credit | Double | Amount that reduced your balance, for example a TRANSFER you sent (the amount plus its fee). 0.0 when the entry did not draw funds. Note the direction: balance = previous balance + debit − credit. |
…transactions[].balance | Double | Balance after the transaction. |
…transactions[].currency | String | Currency code. |
…transactions[].date | String | Transaction date. Format: yyyy-MM-dd HH:mm:ss. |
…transactions[].requestId | Long | The Request-id you sent when creating the transfer, echoed here so you can reconcile this line against your own record. null for entries with no originating request of yours, such as a TOPUP. |
…currentBalance | Object | Current balance for the currency. |
…currentBalance.currency | String | Currency code. |
…currentBalance.balance | Double | Balance amount. |
…currentBalance.date | String | Format: yyyy-MM-dd HH:mm:ss. |
{
"status": true,
"code": null,
"dialog": null,
"extra": null,
"data": {
"list": [
{
"transactions": [
{
"id": "",
"type": "TOPUP",
"debit": 1000.0,
"credit": 0.0,
"balance": 1000.0,
"currency": "USD",
"date": "2026-07-20 15:14:00",
"requestId": null
},
{
"id": "efi 2600000006",
"type": "TRANSFER",
"debit": 0.0,
"credit": 101.0,
"balance": 899.0,
"currency": "USD",
"date": "2026-07-21 12:25:14",
"requestId": 1720598400123
},
{
"id": "efi 2600000007",
"type": "TRANSFER",
"debit": 0.0,
"credit": 10.1,
"balance": 888.9,
"currency": "USD",
"date": "2026-07-21 12:30:20",
"requestId": 1720598400124
}
],
"currentBalance": {
"currency": "USD",
"balance": 888.9,
"date": "2026-07-21 12:35:00"
}
}
]
}
}List payment modes
Retrieve the payment modes supported by your account.
curl 'https://sandbox.woocash.money/api/api/woo/send/money/modes' \
-H 'username: YOUR_USERNAME' \
-H 'key: YOUR_KEY' \
-H 'Request-id: 1720598400123'Request body: None.
{
"status": true,
"code": null,
"dialog": null,
"extra": null,
"data": ["CASH_COLLECTION", "MOBILE_WALLET"]
}Callbacks
callbackUrl applies to Cash Collection mode only. Mobile Wallet does not use callbacks: its outcome is determined instantly by the Create a Transfer response, which either succeeds or fails with no intermediate statuses to track, so there is nothing to notify later. See Payment modes.
In Cash Collection mode a transfer moves through several statuses (SENT, RECEIVED, CANCELED_FULL), so Whish Money needs a way to tell you when the transaction changes. When you provide a callbackUrl in the Create a Transfer request, Whish Money calls it automatically on one transition only: SENT to RECEIVED. That is the only callback you receive without asking for it. No other status produces a callback, including CANCELED_FULL, so do not wait on one for a cancellation: confirm it with Get transfer status, which is authoritative for every status.
Callback source IPs
Whish Money calls your callbackUrl from a fixed set of source IPs (Whish Money → you). 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.
Cash Collection transfer statuses
| Status | Meaning |
|---|---|
SENT | The transfer has been sent and is available for collection. |
RECEIVED | The receiver has collected the transfer. |
CANCELED_FULL | The transfer has been fully canceled. |
Callback request
Whish Money sends a POST with Content-Type: application/json to your callbackUrl, with two additional headers:
| Header | Type | Description |
|---|---|---|
timestamp | Long | Time the callback was sent, in epoch milliseconds. |
signature | String | Base64-encoded signature of the request, sent as the bare value with no prefix. The signing scheme is not published yet; ask Whish Money before relying on it. |
Callback body
| Field | Type | Description |
|---|---|---|
date | String | Human-readable time of the event. |
externalId | Long | The numeric Request-id you sent on Create a Transfer, echoed back exactly as you sent it so you can match this callback to your record. Sent as a JSON number, not a quoted string. |
transferId | String | Identifier of the transfer this callback is about. |
status | String | The transfer status this callback reports, for example SENT, RECEIVED, or CANCELED_FULL. |
The callback body is a flat object. It does not use the status / code / data envelope that API responses use, so status here is the String transfer status directly, not the Boolean call result. It reports the same value you would read from data.status on Get transfer status.
Content-Type: application/json
timestamp: 1772094998000
signature: RzqRXZBTc/XiX1wYAXa6zIKcUNOkIiCwflfkcJCRXJWI+4d95eIZBdI8eRZwsshTi8HubfJTzkHQ/ApbDxcw+Q=={
"date": "Thu Feb 26 10:36:38 EET 2026",
"externalId": 1720598400123,
"transferId": "TEST 2600000014",
"status": "RECEIVED"
}Respond with HTTP 200 to acknowledge receipt. Treat the callback as a notification rather than proof: confirm anything you act on against Get Transfer Status, which is authoritative.
A callback reports the transfer's current status rather than announcing something new, so a repeat is not a new event. Reading status and applying it leaves you where you already were, which is why polling is safe. The one thing to guard is a side effect you only want to happen once, such as crediting an order or releasing goods: trigger those on the change of status you record, not on the arrival of a POST.
Reference data
Currencies
| Currency | Id |
|---|---|
| USD | 2 |
| LBP | 1 |
Countries
Receiving is always Lebanon (id = 1, code LB), because this API delivers funds in Lebanon. The sending countries enabled for your account come from List Sending Countries; pull them from there rather than hard-coding a list.
Payment modes
| Value | Description |
|---|---|
CASH_COLLECTION | Receiver collects cash from a Whish Money agent using the ltnNumber. |
MOBILE_WALLET | Funds are credited directly to a Whish mobile wallet account. |
Source of Funds (SoF)
| Value | Description |
|---|---|
| Salary | Income earned from employment, wages, or regular payroll. |
| Income from Own Business | Revenue generated from operating a personal or self-owned business. |
| Income from Investments | Returns or profits from investments such as stocks, bonds, dividends, or interest. |
| Rent | Income received from renting out property, land, or other assets. |
| Inheritance | Funds received from the estate of a deceased individual. |
| Savings | Money accumulated and withdrawn from personal savings or bank accounts. |
| Sale of Property or Goods | Proceeds from selling physical property, assets, or personal goods. |
| Loan | Money borrowed from financial institutions, individuals, or other entities. |
| Lottery / Gambling | Funds from lottery winnings, betting, or other gambling activities. |
| Other | Any source of funds not covered by the categories above. |
Reason
| Value | Description |
|---|---|
| Donations & Gifts | Voluntary donations or personal gifts to individuals or organizations. |
| Education | Educational expenses such as tuition, fees, books, or training programs. |
| Family Support | Financial assistance to family members for living or personal needs. |
| Medical Treatment | Healthcare services, treatments, medication, or hospital fees. |
| Purchase of Goods | Buying goods, products, or merchandise. |
| Travel Expenses | Costs associated with travel spending. |
| Insurance Plan / Claim | Insurance premiums, or funds received/used as part of an insurance claim. |
| Retirement / Pension | Contributions to or withdrawals from retirement or pension funds. |
| Other | Any reason not covered by the categories above. |
Send reason and sourceOfFunds as the exact values listed in the tables above (for example reason: "Donations & Gifts", sourceOfFunds: "Income from Own Business").
Error codes
When status = false, the code field carries one of the identifiers below. Surface dialog.message to end users and branch your logic on code. Error codes are literal API values and are reproduced exactly, including their original spelling.
| Code | Description | Endpoint(s) |
|---|---|---|
api.account.invalid_request | Invalid account request. | All |
amount.not_allowed | The amount is less than the price or is negative. | Fees |
voucher_amount.not_allowed | A voucher on your account could not be applied to this amount. | Fees |
transfer.invalid_amount | Amount not within the price list / negative. | Fees, Send, Activity |
receiver.empty | Receiver information is missing. | Send |
receiver.invalid.phone_number | Receiver phone number is invalid. | Send |
receiver.customer.invalid_name | Receiver name is invalid. | Send |
receiver.customer.info_invalid | Receiver information is invalid. | Send |
receiver.customer.name_enlgish | Receiver name must be in English. | Send |
receiver.customer.under_age | Receiver is underage. | Send |
receiver.customer.date_of_birth_invalid | Receiver identity.dateBirth is missing, not in dd-MM-yyyy, or in the future. Only checked when you send receiver.identity. | Send |
receiver.whish.account.not.exists | Whish account does not exist (Mobile Wallet). | Send |
receiver.unknown.error | The wallet credit failed for an unspecified reason (Mobile Wallet). | Send |
api.fetching.price_error | Pricing could not be retrieved for the transfer (Mobile Wallet). | Send |
sender.invalid.phone_number | Sender phone number is invalid. | Send |
sender.invalid.phone.number | sender.phone was empty. Note the spelling: this code uses dots throughout, unlike sender.invalid.phone_number above, which is a different check. | Send |
sender.customer.invalid_name | Sender name is invalid. | Send |
sender.customer.info_invalid | Sender information is invalid. | Send |
sender.customer.name_enlgish | Sender name must be in English. | Send |
sender.customer.date_of_birth_invalid | Sender identity.dateBirth is missing, not in dd-MM-yyyy, or in the future. Only checked when you send sender.identity. | Send |
sender.missing_info | Sender information is missing. | Send |
sender.invalid.date.of.birth | Invalid date of birth. | Send |
sender.kyc.needed | Sender's KYC is required. | Send |
receiver.kyc.needed | Receiver's KYC is required. | Send |
sender.interview.needed | Additional information required for the sender. | Send |
receiver.interview.needed | Additional information required for the receiver. | Send |
currency.not_supported | Currency not supported. | Send |
transfer.reason_empty | Reason not provided. | Send |
transfer.sourceOfFunds_empty | Source of funds not provided. | Send |
transfer.from_country_not_allowed | Transfers from this country are not allowed. | Send |
transfer.not_allowed_countries | Transfer to these countries is not allowed. | Send |
transfer.account_reach_balance_limit | Reached balance limit / insufficient funds. | Send |
transfer.exceeds_account_transaction_limit | Amount exceeds per-transfer limit. | Send |
transfer.exceeds_account_daily_limit | Reached daily sending limit. | Send |
transfer.exceeds_customer_daily_limit | The receiver has reached their daily receiving limit. Distinct from the account limits above: this one is about the receiving customer, not your account. | Send |
transfer.exceeds_customer_yearly_limit | The receiver has reached their yearly receiving limit. | Send |
transfer.already.done | This Request-id was already used, so no duplicate was created. Expected when resending after a timeout: read the outcome with Get Transfer Status using that requestId. | Send |
transfer.miss_config | Transfer configuration is missing. | Send |
service.inactive | Service is inactive. | Send |
service.price_not_active | Service price is not active. | Send |
service.transaction_status_unknown | Transaction status is unknown. | Send |
service.transaction_failed | Transaction failed. | Send |
transfer.not_exists | Transfer does not exist / id not found. | Cancel, Status |
transfer.cannot_be_canceled | The transfer is not cancellable: it was already canceled or received, or it is a Mobile Wallet transfer, which is final at send time and cannot be canceled. | Cancel |
sending.transfer.invalid.info | Sending transfer info is invalid. | Status |
error | Generic error. | Statement, Modes |
code may carry the numeric value 500 to indicate a Pending transaction. This is common for Mobile Wallet sends; for Cash Collection it is rare and only happens on an internal server error. Treat it as pending in either mode. See Mobile Wallet response interpretation.
Best practices
- Test in Sandbox first. Verify every flow against
sandbox.woocash.moneybefore requesting Production access, and remember the differing base-URL conventions between environments. - Quote before you send. Call Get Transfer Fees to confirm fees and total, then send with the same amount and currency.
- Use a new
Request-idfor each new transfer, and reuse that id to retry it. Generate a unique numeric value (Long, for example epoch milliseconds plus a counter) per transfer and store it before you send. After a timeout, resend the identical request with the same id: either the transfer is created, or you gettransfer.already.doneand read the outcome viarequestId. Retrying with a fresh id is what creates duplicates. - Branch on
status, not HTTP code. All responses return HTTP200; the outcome lives in the body. - Mobile Wallet is instant. Its outcome is final in the Create a Transfer response (no callback). Map
status = false+code = 500to Pending and reconcile later. - Callbacks are Cash Collection only. Configure
callbackUrlfor Cash Collection and update state when the transfer moves fromSENTtoRECEIVED. Confirm any status you act on with Get Transfer Status, which is authoritative. - Escalate only the pending case.
code: 500is the single outcome that warrants follow-up with Whish Money (treat as Pending). Every other non-successcodeis a final failure you resolve automatically. On Get Transfer Status,sending.transfer.invalid.infoortransfer.not_existsmeans the transfer was never registered, so resend the original request with the sameRequest-idbefore querying again. - Use English names. Sender and receiver names must be in English, or the send is rejected (
*.name_enlgish). - Store references. Persist the
Request-idyou sent, plustransferIdandltnNumberfrom the response. Once your integration is live, quote theRequest-idwhen you raise a transaction with Whish Money: it is your own reference, you hold it even when a send times out with no response, and it is the id support will ask for. KeepltnNumbertoo: it is what Cancel a transfer needs, and it is one of the ways to look up status. - Secure your credentials. Keep
username/keyserver-side and out of logs and version control. - Respect limits. Design for per-transfer, daily, and balance limits, and surface
dialog.messageto end users when they are hit. - Reconcile regularly. Use List Account Activity and Get Account Statement to reconcile your ledger against Whish Money.
Sandbox scenarios
Cash Collection
| Scenario | Input | Expected result |
|---|---|---|
| English name | Valid | PASS |
| Arabic name | Invalid | FAIL |
Mobile Wallet
| Scenario | Input | Expected result |
|---|---|---|
| Payment to wallet | 96170123456 | PASS |
| Payment to wallet | 96170123123 | FAIL |
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 |
|---|---|---|
v6.4.9 | 2026-08-21 | Date of birth requirements updated for sender and receiver. |
v6.4.8 | 2026-07-15 | Documentation refinements: sandbox cURL examples, callback reference, and reference clarifications. |
v6.4.7 | 2026-02-16 | Added some error codes. |
v6.4.6 | 2026-02-09 | Minor updates and improvements. |
v6.4.5 | 2026-02-02 | Minor updates and improvements. |
v6.4.4 | 2026-01-28 | DeductFees renamed to deductFees; deductFromAmount replaced by deductFees. |
Postman & tools
Import the ready-made Postman collection for this API, set your username and key as collection variables (or attach a Sandbox / Production environment), and call every endpoint without writing code. Request-id auto-generates a unique numeric value per send.
Download collection OpenAPI spec All 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.