What’s new in V2 of the pawaPay API?

We have simplified the pawaPay API to make it easier to use. We’ve also added clarity to allow providers with different payment authorisation flows to be implemented in a future-proof manner. And we have restructured the payloads to consistently add features and capabilities to the API in the future.

Let’s take a look one by one.

Support for new payment authorisation flows

Some providers do not use the PIN prompt to authorise payments. They either require the customer to preauthorise the payment by generating an OTP or use a redirection based authorisation. Now both these mechanisms are supported in a way that allows you to implement once and all future providers with the same authorisation mechanism can be supported without a new implementation.

Avoiding failures on initiation is now easier

We’ve added all the data you need to our active configuration endpoint to make sure payments do not fail on initiation. There is data there for handling decimals in amounts correctly, minimum and maximum transaction limits, provider availability information etc.

Also the predict provider endpoint helps you not only predict the provider for a phone number, but also sanitises and validates the phone number to ensure it’s valid for the country and provider.

The best customer experience

The active configuration endpoint also includes information about the specifics of how payments are authorised with the particluar provider. This allows you to show the exact steps the customer needs to take and how to recover from cases where they don’t enter the PIN prompt in time etc.

Active configuration gets an overhaul

We beefed up the active configuration endpoint. It is more logically structured now and supports filtering to get information on the exact countries and flows you need in your payment flow.

We’ve added a lot of static information like provider names and logos, currency names, country codes, flags and prefixes. This way you are able to build a dynamic payment flow that does not need any changes when new providers are enabled on your account.

How to upgrade from V1?

Let’s first take a look at how to make everything work just as it was before. This is not a large undertaking.

1

Change the baseUrl of the API

If you are still using api.pawapay.cloud as the baseUrl for the API, now it’s time to change it to:

  • api.sandbox.pawapay.io for your sandbox account
  • api.pawapay.io for your live account

The V2 API is not any more available through the .cloud domain.

2

Versioned paths

Update all your request paths to include the version number.

For example, deposits in V1 were at:

    POST https://api.sandbox.pawapay.io/deposits

Now in V2 they are:

    POST https://api.sandbox.pawapay.io/v2/deposits
3

Financial requests

When making calls to initiate deposits, payouts and refunds you should:

  1. Rename all instances of correspondent to provider.
  2. Move it inside the accountDetails object.
  3. Change the type within payer or recipient from value MSISDN to MMO.
  4. Change the address within payer or recipient to accountDetails.
  5. Change the value within address to phoneNumber.
  6. Remove the customerTimestamp from the request payload. This parameter has been removed.
  7. Rename statementDescription to customerMessage.
  8. Change metadata from "fieldName": "orderId" and "fieldValue": "ORD-123456789" format to "orderId": "ORD-123456789" format.

Let’s take a look at a before and after example.

Before in V1:

    {
        "depositId": "919b7674-b61d-4b69-ac58-d74d2b089192",
        "amount": "15",
        "currency": "ZMW",
        "correspondent": "MTN_MOMO_ZMB",
        "payer": {
            "type": "MSISDN",
            "address": {
                "value": "260763456789"
            }
        },
        "customerTimestamp": "2020-02-21T17:32:28Z",
        "statementDescription": "Note of 4 to 22 chars",
        "metadata": [
            {
                "fieldName": "orderId",
                "fieldValue": "ORD-123456789"
            },
            {
                "fieldName": "customerId",
                "fieldValue": "customer@email.com",
                "isPII": true
            }
        ]
    }

Now in V2:

    {
        "depositId": "919b7674-b61d-4b69-ac58-d74d2b089192",
        "amount": "15",
        "currency": "ZMW",
        "payer": {
            "type": "MMO", //Was "MSISDN"
            "accountDetails": { //Was address
                "phoneNumber": "260763456789", //Was value
                "provider": "MTN_MOMO_ZMB" //was correspondent
            }
        },
        "customerMessage": "Note of 4 to 22 chars", //Was statementDescription
        "metadata": [
            {
                "orderId": "ORD-123456789" //Was in annoying format
            },
            {
                "customerId": "customer@email.com", //This one as well
                "isPII": true
            }
        ]
    }

All financial requests should now be in the new format.

Let’s take a look at responses, callbacks and status checks.

4

Required changes in responses

The only required change in responses is for payouts. We do not return the status of ENQUEUED any more on initiation. The only statuses returned on initiation are:

  • ACCEPTED
  • REJECTED
  • DUPLICATE_IGNORED

If you require to know whether the payout was enqueued, you can use the check payout status endpoint.

Also, the rejectionReason and error responses are now consolidated into failureReason.

    {
        "status": "REJECTED",
        "failureReason": {
            "failureCode": "MISSING_PARAMETER",
            "failureMessage": "Request does not include the required parameter 'provider'."
        }
    }

We will go over the changes in failureReason later in the guide.

Next let’s look at callbacks and status checks.

5

New response format for status checks

We’ve changed the response payload of status check for deposits, payouts and refunds to determine the existence of a payment more clearly.

The response will now include the status of the request with possible values of FOUND and NOT_FOUND. If FOUND the payment data will be available in the data.

    {
        "status": "FOUND",
        "data": {
            "depositId": "919b7674-b61d-4b69-ac58-d74d2b089192",
            "status": "COMPLETED",
            "amount": "15.00",
            "currency": "ZMW",
            "country": "ZMB",
            "payer": {
                "type": "MMO",
                "accountDetails": {
                    "phoneNumber": "260763456789",
                    "provider": "MTN_MOMO_ZMB"
                }
            },
            "customerMessage": "Note of 4 to 22 chars",
            "clientReferenceId": "",
            "created": "2025-06-02T12:20:35Z",
            "providerTransactionId": "09ae7c99-f351-4c87-a6d2-286337a70d21",
            "metadata": {
                "orderId": "ORD-123456789",
                "customerId": "customer@email.com"
            }
        }
    }
6

Payload changes for callbacks and status checks

We have consolidated rejectionReason, failureReason and error responses to a failureReason. You can now always find out what happened from a single place which is the failureReason.

You can find all the failureCodes in our docs. Make sure you have implemented handling for them.

The requestedAmount and depositedAmount are now just a single amount, showing the amount that was requested to be deposited. If the payment FAILED, no funds have been deposited.

The respondedByPayer, receivedByRecipient and suspiciousActivityReport have been removed from all callbacks and status checks.

The providerTransactionId replaces the correspondentIds object. It will contain the ID of the transaction as specified by the provider (if available from the provider).

Otherwise, all the changes in the request will be reflected in the callbacks and status checks as well, so make sure renamed or deprecated parameters are adjusted.

Before:

    {
        "depositId": "919b7674-b61d-4b69-ac58-d74d2b089192",
        "status": "FAILED",
        "requestedAmount": "15",
        "depositedAmount": "0",
        "currency": "ZMW",
        "country": "ZMB",
        "correspondent": "MTN_MOMO_ZMB",
        "payer": {
            "type": "MSISDN",
            "address": {
                "value": "260763456789"
            }
        },
        "customerTimestamp": "2020-02-21T17:32:28Z",
        "statementDescription": "Note of 4 to 22 chars",
        "created": "2020-02-21T17:32:29Z",
        "respondedByPayer": "2020-02-21T17:32:30Z",
        "correspondentIds": {
            "MTN_INIT": "ABC123",
            "MTN_FINAL": "DEF456"
        },
        "suspiciousActivityReport": [
            {
            "activityType": "AMOUNT_DISCREPANCY",
            "comment": "There is a discrepancy between requested and actual deposit amount."
            }
        ],
        "failureReason": {
            "failureCode": "OTHER_ERROR",
            "failureMessage": "Payers address is blocked"
        },
        "metadata": {
            "orderId": "ORD-123456789",
            "customerId": "customer@email.com"
        }
}

After implementing the above changes, all the financial flows should work just like before. Let’s look at the other endpoints also.

7

Callback resends, enqueued payout cancellation etc

We have unified the following endpoints by moving the respective payment ID from request body to query params:

For example resending deposit callback in V1:

    POST https://api.sandbox.pawapay.io/deposits/resend-callback

    {
       "depositId": "919b7674-b61d-4b69-ac58-d74d2b089192"
    }

Now in V2:

    GET https://api.sandbox.pawapay.io/deposits/resend-callback/919b7674-b61d-4b69-ac58-d74d2b089192

    //No body

Let’s take a look at some of the other endpoints and how they have changed.

8

Provider availability

Most importantly, the provider availability endpoint now only returns the countries and providers that have been configured on your pawaPay account. This also means that you now need to authenticate calls to it. The same API token can be used to authenticate as all other endpoints. For a refresher, check out the How to authenticate section of our docs.

We’ve also added filtering options for both countries and operation types, so that you can fetch provider availability information only for the flow you are interested in at the moment.

    GET https://api.sandbox.pawapay.io/v2/availability?country=ZMB&operationType=DEPOSIT

    [
        {
            "country": "ZMB",
            "providers": [
                {
                    "provider": "MTN_MOMO_ZMB",
                    "operationTypes": {
                        "DEPOSIT": "OPERATIONAL"
                    }
                },
                {
                    "provider": "AIRTEL_OAPI_ZMB",
                    "operationTypes": {
                        "DEPOSIT": "OPERATIONAL"
                    }
                }
            ]
        }
    ]

As you can see, correspondent has been renamed to provider everywhere in the API.

Next let’s take a look at predict provider.

9

Predicting the provider

Three changes have been done for this.

  • The correspondent has been renamed to provider.
  • The operator has been omitted in the response.
  • The msisdn has been renamed to phoneNumber.

Next, let’s take a look at wallet balances.

10

Retrieving wallet balances

To unify filtering across the API, when filtering by country, you can now use the query param country to do that instead of a path parameter.

Checking wallet balances before:

    GET https://api.sandbox.pawapay.io/v1/wallet-balances/ZMB

    {
        "balances": [
            {
            "country": "ZMB",
            "balance": "21798.03",
            "currency": "ZMW",
            "mno": ""
            }
        ]
    }

Now in V2:

    GET https://api.sandbox.pawapay.io/v2/wallet-balances?country=ZMB

    {
        "balances": [
            {
                "country": "ZMB",
                "balance": "42867.47",
                "currency": "ZMW",
                "provider": ""
            }
        ]
    }

As you can see, the mno has been renamed to provider as well.

Now let’s look at active configuration.

11

Getting your active configuration

The merchantId has been removed from the response.

The merchantName has been renamed to companyName.

Of course the correspondents have been renamed to providers like in the rest of the API.

Within the list of providers you can now find the list of currencies they support. Currently, the only country with multicurrency support is DRC.

In currencies you will now find a map of operationTypes which contain the minimum and maximum transaction limits. The minTransactionLimit and maxTransactionLimit have been renamed to minAmount and maxAmount respectively.

The ownerName has been renamed to nameDisplayedToCustomer.

Getting your active configuration before:

    {
        "merchantId": "DEMO", //Removed in V2
        "merchantName": "DEMO",
        "countries": [
            {
                "country": "BEN",
                "correspondents": [
                    {
                        "correspondent": "MOOV_BEN",
                        "currency": "XOF",
                        "ownerName": "PAWAPAY",
                        "operationTypes": [
                            {
                                "operationType": "DEPOSIT",
                                "minTransactionLimit": "100",
                                "maxTransactionLimit": "2000000"
                            },
                            ...
                        ]
                    }
                ]
            }
        ]
    }

Now in V2:

    {
        "companyName": "DEMO", //Was merchantName
        "countries": [
            {
                "country": "BEN",
                ...
                "providers": [ //Was correspondents
                    {
                        "provider": "MOOV_BEN",
                        "nameDisplayedToCustomer": "PAWAPAY", //Was ownerName
                        ...
                        "currencies": [ //Currencies are now an array
                            {
                                "currency": "XOF",
                                "operationTypes": { //Now as a map
                                    "DEPOSIT": {
                                        "minAmount": "100", //Was minTransactionLimit
                                        "maxAmount": "2000000", //Was maxTransactionLimit
                                        ...
                                    },
                                    ...
                                }
                            }
                        ]
                    }
                ]
            }
        ]
    }

Currently we went over the minimal changes needed when upgrading to V2. Later we will review what has been added to the endpoint to help build a more dynamic and high quality payment flow.

Next let’s take a look at the payment page.

12

When using the payment page

The payment page also has some small changes needed to upgrade it to V2.

The msisdn has been renamed to phoneNumber.

The statementDescription has been renamed to customerMessage.

The amount is now contained in the amountDetails object together with the currency.

Before in V1:

    {
        "depositId": "5fcea42c-bbe2-469f-9c59-179f6363d0d5",
        "returnUrl": "https://merchant.com/returnUrl",
        "statementDescription": "Note of 4 to 22 chars",
        "amount": "15",
        "msisdn": "260763456789"
    }

Now in V2:

    {
        "depositId": "5fcea42c-bbe2-469f-9c59-179f6363d0d5",
        "returnUrl": "https://merchant.com/returnUrl",
        "customerMessage": "Note of 4 to 22 chars",
        "amountDetails": {
            "amount": "15",
            "currency": "ZMW"
        },
        "phoneNumber": "260763456789"
    }

Failures of initiating the payment page have also been unified into failureReason like the rest of the API.

When checking for the final status of the payment, the changes listed earlier in the guide must be implemented to ensure everything works.

13

And done!

Now everything should be upgraded to V2 and working just like it was before.

Now let’s delve into the benefits of doing so.

How to get the best out of V2?

We’ve added some capabilities that allow for a more dynamic, lower maintenance integration. We’ve also enabled a better customer experience for your customers. All API first as we do, so you can build exactly what you want.

1

Support for different payment authorisation flows

There are some providers like Orange in Burkina Faso and Wave in Senegal and Ivory Coast that do not use the common PIN prompt mechanism for authorising payments.

We’ve now made it easy to implement authorisation flows for providers with

  • preauthentication
  • redirection You can implement each of those once and know that new providers with the same authorisation flows will work as well.

There’s a step by step guide with example of how to do that in the Deposits guide.

2

Provider specific instructions for authorisation

When initiating deposits with most providers, the PIN prompt will pop up automatically on the payers phone. In case the PIN prompt times out before the customer is able to authorise it, some of the providers allow the PIN prompt to be revived.

Also, some providers require the customer to dial a USSD code and go through the USSD menu to authorise their payment by entering their PIN.

To be able to show the customer exactly what they should do after they’ve “pressed pay”, we have included all that information into our active configuration endpoint.

We have it all explained in our step-by-step guide for deposits.

3

Making sure payment data is accurate and valid

Ensuring that the data in your deposit and payout initiations is valid improves the success rates.

You can use the predict provider endpoint to both sanitise and format the phone number to make sure it’s valid. The endpoint also predicts the correct provider for the phone number. This way you can preselect the provider reducing failures due to wrong provider selection and reducing the amount of clicks for your customers.

Also as part of the active configuration endpoint, you have not only the minimum and maximum transaction amounts for all operations, but also information on whether decimals are supported or not.

This is all covered in the step-by-step guide for deposits. Of course we have the same available for payouts and refunds as well.

4

Easier support for showing provider availability

Providing information to customers on which providers are currently experiencing downtime and not available for payments allows for a much better customer experience.

We’ve both made it easier to get the specific information you need from provider availability endpoint, but it is also a part of active configuration now. This way it’s even easier to show availability information to your customers when you are setting up their payment experience.

You can read more how to do that from the step-by-step guide for deposits.

5

Making it easy to expand

It should be as easy as possible to expand into new countries and add new providers as they become available.

We’ve added lots of information to active configuration endpoint to make it as dynamic and easy as possible. We have provider names and logos, country flags and prefixes. All the transaction limits and decimals support in amounts. Company name shown on the PIN prompt, currencies and their commonly used abbreviations etc. You can fetch all that information when rendering the payment flow to your customer reducing the amount of effort required to add providers or expand into new markets.