cURL

Payout API

ENDPOINTS:

POST /v1/payout
GET /v1/payout/:id
GET /v1/payouts/search
POST /v1/payout-batch
GET /v1/payouts-batch/:id
POST /v1/payout/:id/reverse

Wave's Payout API provides a programmatic way to send money from your business to one or more recipients. Payout recipients are identified by mobile, so using the API is as simple as telling Wave:

"Send amount to mobile number."

See also:

Base URL

All the endpoint paths referenced in the API document are relative to a base URL, https://api.wave.com.

Authentication

Authenticating to the API is done via API keys. These keys are bound to a single business wallet and can only interact with it.

If you need to interact with more than one wallet in your network, then you will have to obtain one key per wallet.

Each request must be sent over HTTPS and contain an authorization header specifying the bearer scheme with the api key:

Authorization: Bearer wave_sn_prod_YhUNb9d...i4bA6

Note that the actual key is much longer but for documentation purposes most of the characters have been replaced by an ellipses.

Obtaining your API key

You can manage API keys in the developer's section in the Wave Business Portal. You can create, view, and revoke keys, and define which specific APIs each key has access to.

Dev Portal

When you create a new API key, you will only see the full key once. Make sure you copy it without missing any characters, since it will be masked afterwards for security reasons.

Create API Key

Wave doesn't know your full key, but if you contact API support we can identify them by the last 4 letters that you see displayed in the business portal.

Error codes

In the authentication phase, one of the following errors can cause your request to return early:

Status Code Descriptions
401 missing-auth-header Your request should include an HTTP auth header.
401 invalid-auth Your HTTP auth header can't be processed.
401 api-key-not-provided Your request should include an API key.
401 no-matching-api-key The key you provided doesn't exist in our system.
401 api-key-revoked Your API key has been revoked.
403 invalid-wallet Your wallet can't be used in this particular API.
403 disabled-wallet Your wallet has been temporarily disabled. You should still be able to use the endpoint to check your balance.

Idempotency

Idempotency-Key: 65f735b4-b44b-429d-b0a8-550701e2393a

Every request that modifies data must provide an idempotency key in order to guarantee safe retries without accidentally sending money twice. It identifies what you consider the same request.

Generating random strings

Shell

openssl rand -hex 8

PHP

$idem_key = uniqid(more_entropy: True)

Python

import uuid
idem_key = str(uuid.uuid4())

JavaScript

let idem_key = crypto.randomUUID();

The idempotency key is passed in the HTTP header of a request, in the form Idempotency-Key: <STRING_OF_YOUR_CHOICE>. You will find complete request examples in the API section.

The idempotency key is generated by you, the client. It should be a unique value of up to 255 characters. We suggest using v4 UUIDs or other random strings with sufficient entropy to avoid collisions.

We suggest waiting a few seconds between retries. Please refer to the Retries section for more detailed information on what and how to implement retries.

Rate limiting

Wave APIs are rate limited to prevent abuse that would degrade performance for all users. If you send many requests in a short period of time, you may receive 429 error responses.

API

Create a payout

POST /v1/payout

curl -X POST \
  --url https://api.wave.com/v1/payout \
  -H 'Authorization: Bearer wave_sn_prod_YhUNb9d...i4bA6' \
  -H 'Content-Type: application/json' \
  -H 'idempotency-key: 65f735b4-b44b-429d-b0a8-550701e2393a' \
  -d '{ "currency": "XOF",
        "receive_amount": "500",
        "name": "Fatou Ndiaye",
        "mobile": "+221555110219"
      }'

Send a single payout, i.e. transfer money from your business wallet to a specific recipient, identified by phone number. Executes synchronously, meaning that the Wave will attempt to execute the transaction immediately and return the result in the response of this request.

Parameters

Key Type Description
aggregated_merchant_id String (required for Aggregators) The id of an aggregated merchant identity to use for this payout. This is only available to specific businesses and will return an error if you provide it but don't have permission. If you would like to use this feature, please contact your Wave support representative.
client_reference String (optional, up to 255 characters) A unique string that you provide which can be used to correlate the payout in your system.
currency Currency code The amount currency. Note that your business wallet and the recipient must be in the same country.
mobile Phone number The recipient mobile phone number.
name String (optional), up to 255 characters The recipient name, may be used for user verification.
national_id String (optional, up to 255 characters) An optional field to save the recipient's national ID, for further user verification.
payment_reason String (optional), up to 40 characters An optional message with a payment reason that is shown to customers in the payment receipt.
receive_amount Amount The amount to be paid out to the recipient, net of fees.

Successful result example

{
  "id": "pt-185b5e4b8100c",
  "currency": "XOF",
  "receive_amount": "500",
  "fee": "5",
  "mobile": "+221555110219",
  "name": "Fatou Ndiaye",
  "status": "succeeded",
  "timestamp": "2022-06-20T17:17:11Z"
}

In case there aren't any validation or pre-check errors, posting a payout returns the same fields that you submitted in addition to the following:

Key Type Description
id String A unique identifier for the payout object. Up to 20 characters.
fee Amount The fee for sending the payout.
payout_error Payout error (optional) Details about the reason for a failed payout. Only populated when the status is failed.
status Payout state The status of the payout: processing, failed, or succeeded.
timestamp Timestamp The time and date that this payout request was recorded in our system. Note that this doesn't describe when payments were executed, only when they were submitted.

In case the request fails to pass validation rules or other checks where no Payout object can be created, then a plain top-level error is returned with the fields code and message.

Error result example

{
  "code": "request-validation-error",
  "message": "An 'Idempotency-Key' header is required for POST requests"
}

See the Errors section for a complete list and explanation of each possible error code.

Retrieve a payout

GET /v1/payout/:id

curl -X GET \
  --url https://api.wave.com/v1/payout/pt-185sewgm8100t \
  -H 'Authorization: Bearer wave_sn_prod_YhUNb9d...i4bA6'

Example response

{
    "id": "pt-185sewgm8100t",
    "currency": "XOF",
    "receive_amount": "15000",
    "fee": "150",
    "mobile": "+221555110233",
    "name": "Moustapha Mbaye",
    "national_id": "1751197904376",
    "client_reference": "FAH.4827.1734",
    "payment_reason": "Salary November 2022",
    "status": "succeeded",
    "timestamp": "2022-06-21T09:56:29Z"
    "aggregated_merchant_id": "am-7lks22ap113t4",
}

Retrieves a single payout.

Return attributes

Key Type Description
id String A unique identifier for the payout object. Up to 20 characters.
currency Currency code The amount currency. Note that your business wallet and the recipient must be in the same country.
fee Amount The fee for sending the payout.
mobile Phone number The recipient mobile phone number.
name String (optional), up to 255 characters The recipient name, may be used for user verification.
national_id String (optional), up to 255 characters An optional field to save the recipient's national ID, for further user verification.
payout_error Payout error (optional) Details about the reason for a failed payout. Only populated when the status is failed.
receive_amount Amount The amount to be paid out to the recipient, net of fees.
status Payout state The status of the payout: processing, failed, or succeeded.
timestamp Timestamp The time and date that this payout request was recorded in our system. Note that this doesn't describe when payments were executed, only when they were submitted.
client_reference String (optional), up to 255 characters A unique string that you provide which can be used to correlate the payout in your system.
payment_reason String (optional), up to 40 characters An optional message with a payment reason that is shown to customers in the payment receipt.
aggregated_merchant_id String (optional) The aggregated merchant ID used for this payout, if any.

Search payouts

GET /v1/payouts/search

curl -X GET \
  --url https://api.wave.com/v1/payouts/search?client_reference=FAH.4827.1734 \
  -H 'Authorization : Bearer wave_sn_prod_YhUNb9d...i4bA6'

Example response

{
  "result": [
      {
        "id": "pt-185sewgm8100t",
        "currency": "XOF",
        "receive_amount": "15000",
        "fee": "150",
        "mobile": "+221555110233",
        "name": "Moustapha Mbaye",
        "national_id": "1751197904376",
        "client_reference": "FAH.4827.1734",
        "payment_reason": "Salary November 2022",
        "status": "succeeded",
        "timestamp": "2022-06-21T09:56:29Z"
        "aggregated_merchant_id": "am-7lks22ap113t4",
      }
  ]
}

Retrieves a list of payouts based on the provided query parameters. Currently supports only search by client reference (client_reference).

Return attributes

Key Type Description
result List of payouts The list of payouts that match the search criteria.

Create a payout batch

POST /v1/payout-batch/

curl -X POST \
  --url https://api.wave.com/v1/payout-batch \
  -H "authorization: Bearer wave_sn_prod_YhUNb9d...i4bA6" \
  -H 'content-type: application/json' \
  -H 'idempotency-key: 65f735b4-b44b-429d-b0a8-550701e2393a' \
  -d '{"payouts": [
        { "currency": "XOF",
            "receive_amount": "1000",
            "name": "Fatou Ndiaye",
            "mobile": "+221555110219"
        },
        { "currency": "XOF",
            "receive_amount": "1200",
            "name": "Moustapha Mbaye",
            "mobile": "+221555110233",
            "aggregated_merchant_id": "am-7lks22ap113t4",
        },
        { "currency": "XOF",
            "receive_amount": "16000",
            "name": "Mame Diop",
            "mobile": "+221555144081"
        }
      ]}'

Example response

{
  "id": "pb-185skxq8g1006"
}

Submitting a payout batch means that you request one or multiple payouts to be executed. The processing of these transactions is asynchronous, meaning that this endpoint will not immediately return the resulting payouts.

Instead, you receive an ID that you can then use to retrieve the payout batch result, to see which transactions have completed successfully. We recommend that you poll this endpoint every couple of seconds, depending on the size of your batch.

Parameters

Key Type Description
payouts List of payout requests The list of payouts to be sent. Each item should have the same structure used for a single payout request.

Return attributes

Key Type Description
id String The ID of the payout batch that you can use to poll the get payout batch endpoint.

Retrieve a payout batch

GET /v1/payout-batch/:id

curl -X GET \
  --url https://api.wave.com/v1/payout-batch/pb-185skxq8g1006 \
  -H 'Authorization: Bearer wave_sn_prod_YhUNb9d...i4bA6'

Example response

{
  "id": "pb-185skxq8g1006",
  "status": "complete",
  "payouts": [
    {
      "id": "pt-185skxq9g100w",
      "currency": "XOF",
      "receive_amount": "1000",
      "fee": "10",
      "mobile": "+221555110219",
      "name": "Fatou Ndiaye",
      "status": "succeeded",
      "timestamp": "2022-06-21T10:07:30Z"
    },
    {
      "id": "pt-185skxqa0100y",
      "currency": "XOF",
      "receive_amount": "1200",
      "fee": "10",
      "mobile": "+221555110233",
      "name": "Moustapha Mbaye",
      "status": "processing",
      "aggregated_merchant_id": "am-7lks22ap113t4",
      "timestamp": "2022-06-21T10:07:30Z"
    },
    {
      "id": "pt-185sw98jg1016",
      "currency": "XOF",
      "receive_amount": "16000",
      "fee": "160",
      "mobile": "+221555144081",
      "name": "Mame Diop",
      "status": "failed",
      "payout_error": {
        "error_code": "recipient-limit-exceeded",
        "error_message": "The recipient has reached their monthly limit."
      },
      "timestamp": "2022-06-21T10:25:46Z"
    }
  ]
}

Retrieves a payout batch.

Return attributes

Key Type Description
id String The ID of the payout batch.
payouts List of payout results. The list of payouts, with each item having the same structure as a single retrieved payout.
status Payout batch state The status of the payout batch: processing or complete.

There is no success or failed status on payout batches, because within the same batch, various individual payouts can succeed or fail. In order to handle errors, each payout in payouts should be inspected to check if it contains a payout_error field.

Reverse a payout

POST /v1/payout/:id/reverse

curl -X POST \
  --url https://api.wave.com/v1/payout/pt-185sewgm8100t/reverse \
  -H 'Authorization: Bearer wave_sn_prod_YhUNb9d...i4bA6'

Example response

200 OK

Reverses a previously executed payout, including fees. Currently there is an exact 3 days limit (to allow 1 day margin after the weekend) for reversing payouts, counting from the time they were created. The reference for this is the timestamp field on the Payout object.

This endpoint's idempotency means that if you try to reverse an already reversed payout, then you will get a success return code, but no additional transaction will be created. You can therefore never reverse a payout twice by accident.

Return attributes

None

If the reversal succeeds, this endpoint returns a 200 HTTP code, and no body.

If the reversal fails, an HTTP code above 400 is returned:

error_code explanation
insufficient-funds The recipient wallet doesn't have enough balance to cover the reversal
payout-reversal-time-limit-exceeded The time window for reversing a payout has passed.
payout-reversal-account-terminated The recipient wallet has been terminated in Wave system.
not-found No payout was found under the wallet linked to your API key.

Every payout can only be reversed once, but since the endpoint is idempotent you can safely retry.

Types

Amount

All amounts are represented as a string. The amount has to be a round number, so it cannot contain decimal places. The following rules apply to valid amounts:

Currency code

Standard ISO 4217 three-letter codes in upper case are used to specify currency. Note: the code for the West African Franc is XOF, not CFA.

Phone number

Phone numbers follow the E.164 standard. They must include a country code preceded by +.

Timestamp

An ISO 8601 date and time.

Payout state

A payout state is represented as a string that matches exactly one of the following values:

Payout batch state

A payout batch state is represented as a string that matches exactly one of the following values:

Payout error

Example

{
  "error_code": "insufficient-funds",
  "error_message": "Insufficient funds in wallet."
}

Payout error is an object. Errors can be returned from the API in two places: either on the top-level when a validation or pre-check fails, or as a payout_error field on an individual payout if something went wrong during the execution.

Key Type Explanation
error_code String You can match on this in your system, to decide how to handle the error. You can find a list of all possible error codes under Errors.
error_message String (optional)

Errors

Payout API errors

Example errors returned by the API

Request error

{
  "error": "request-validation-error",
  "message": "Invalid phone number."
}

Validation error

{
  "code": "request-validation-error",
  "message": "Request invalid",
  "details": [
    {
      "loc": ["currency"],
      "msg": "Unknown currency identifier: ABC. We require the currency to be a three-letter ISO 4217 code.",
      "type": "value_error"
    }
  ]
}

Error when trying to execute a payment

{
  "error_code": "insufficient-funds",
  "error_message": "Insufficient funds in wallet."
}

Error on a Payout object, for example as part of a batch

{
  "id": "pt-185sw98jg1016",
  "currency": "XOF",
  "receive_amount": "16000",
  "fee": "160",
  "mobile": "+221555144081",
  "name": "Mame Diop",
  "status": "failed",
  "payout_error": {
    "error_code": "recipient-limit-exceeded",
    "error_message": "The recipient has reached their monthly limit."
  },
  "timestamp": "2022-06-21T10:25:46Z"
}

The following is the list of errors that can occur when using this API.

error_code explanation
country-mismatch The recipient must be in the same country as your business.
currency-mismatch The currency you specified doesn't match that of your and the recipient's wallet.
idempotency-mismatch You submitted a request with an idempotency key that you have used in a previous request, but the request contents don't match.
insufficient-funds Your business wallet doesn't have the necessary balance to cover the total amount including fees.
internal-server-error A technical error has occurred in Wave's system. We work very hard to avoid this, so it is only reserved for unexpected cases. Please get in touch with your Wave partner representative if you encounter this. You can still retry the request automatically with the same idempotency key. We suggest you wait a couple of seconds between retries.
invalid-aggregated-merchant-id An aggregated merchant ID provided with this request does not exist or cannot be used from this account.
missing-auth-header The request is missing a Bearer token in the Authorization header. See Authentication for instructions.
not-found You requested a payout or a payout batch by ID that we can't match to anything in our system. You should double-check that the ID corresponds to one that you've received in a response, and that the request came from the same business wallet.
recipient-minor The recipient is a minor and cannot receive a payout from this source.
recipient-account-blocked The Wave account you are sending money to is blocked. This is usually due to lost phones or fraud reasons.
recipient-account-inactive The Wave account for the person you are sending money to is inactive. They can re-activate their account by calling Wave support.
recipient-limit-exceeded The person you are sending money to has reached their monthly limits. They can often raise their limits by verifying their identity at a Wave agent.
request-not-json Your request either doesn't have a JSON body, or is lacking the Content-Type header "application/json".
request-parsing-error The JSON body of this request is invalid, generally because of misplaced brackets, commas, or quotes. The error_message will help you with details.
request-validation-error Your request doesn't match the object type required. These are things like a missing field or an invalid type in a provided field like an invalid phone number. The error_message will help you with details.
service-unavailable In some rare and short cases Wave services are down for maintenance. You can try the request again later.
too-many-requests You've sent more requests than we can process in a short time span. You can process your payouts in batches to circumvent this.
aggregated-merchant-required An aggregated merchant identity is required to process the payout. You must provided an aggregated merchant ID.

We suggest that you write code that can handle the errors above. We might occasionally add a new error type.

The list above only covers known errors that are within Wave's control. There could be other technical issues like connection errors or timeouts if there are any network issues between your servers and Wave's. These are too numerous to list, but in any of those cases, requests can be safely retried, as long as the same idempotency key is used.

HTTP Status Codes

The general reason for the failure is reflected in the HTTP response status code. 4xx codes signal a problem with the request from the client and 5xx codes indicate a problem on the server end. Specific details of the problem are provided in the message body.

Some of the status codes we return are listed below.

Code Title Descriptions
400 Bad Request The server cannot process the request because it is badly formed.
401 Unauthorized The API key is invalid.
403 Forbidden The API key doesn't have appropriate permissions for the request.
404 Not Found You requested an object or page that could not be found.
405 Method Not Allowed The combination of URL path and REST method doesn't exist on this API.
408 Request Timeout The request took too long to process. Try again later.
409 Conflict The request could not be completed due to a conflict with the current state of the resource.
422 Unprocessable Entity The request was well-formed, but the server could not process it.
429 Too Many Requests The rate limit was exceeded i.e. the server received too many requests in a given period of time.
500 Internal Server Error The server encountered an error. Try again later, but the error may persist.
503 Service Unavailable The server is unable to handle the request due to a temporary overload or scheduled maintenance. Try again later.
504 Gateway Timeout A timeout occurred on the network while processing a request. Try again later.

Status of a payout

Once a payout is entered in our system, it will be created with the status processing. The payout will be immediately start processing and attempt to send the selected amount to the final recipient, after which the status of the payout will be updated to succeeded or failed. The status of a payout can be obtained at all times through the GET /v1/payout endpoint, by providing the id returned at creation time.

A failed payout will also include the reason why the transfer failed as a payout_error, and you can use this information to determine if it should be retried.

If a payout could not be entered in our system correctly, then querying the GET /v1/payout endpoint with the affected id will result on a not-found error to be returned.

In extraordinary cases, Wave might experiment an outage affecting the APIs, resulting on a 5XX error, such as 500 (Internal Server Error) or 503 (Service Unavailable). If you get this response from the POST /v1/payout endpoint, a payout might still be created on our system but without returning an associated id. You can safely retry the payout in these cases by ensuring the same idempotency key is used.

Retrying transactions

The following are Wave's recommendations on retry logic. Please carefully read the section on Errors and Idempotency.

System errors

Some errors are unexpected, which means the transaction is in an unknown state. This can for example happen during an outage or when there are internet connectivity issues.

Rate limiting errors

Rate limiting errors of type "429 Too Many Requests" should always be retried. Of course you should wait a few seconds before retrying. You should still mark them internally as pending instead of failed.

Validation and balance errors

All other errors are final, so your system can mark the transaction as failed.

To summarize: You should generally retry on unexpected errors like connection problems and unavailable services. Always use the same idempotency key when retrying a payout or batch.

Changelog

2023-06-16

2022-11-24

2022-10-17

2022-09-23

2022-09-21

2022-09-08

2022-06-27