Virtual Accounts
Payouts

Payouts

At any time you can pay out funds from you Virtual Account to an external account.

Initiating Payout

You can initiate Payout in two ways:

  • from Merchant Portal, Balances section
  • using Volume API

Initiating Payout in Merchant Portal

In order to make a Payout, please go to Balances section and press Make Payout next to the account you want to make payment from.

BalancesBeforePayment

PayoutDialog

Once you've made the payment and it has been finished, you'll see change in balance reflected in the same Balances page. BalancesAfterPayment

Initiating Payout using Volume API

Path

{{api-host}}/api/merchants/{{merchant-id}}/applications/{{application-id}}/payouts

Headers

HeaderValue
x-app-nameapplication name
x-application-idapplication-id
x-application-secretapplication-secret
Content-Typeapplication/vnd.volume.v0.7+json
Acceptapplication/vnd.volume.v0.7+json

Request Body

{
    "payInAccountId": 10,
    "amount": 50
}

Response Body

{
    "payoutPaymentId": "ea0d978b-6502-4d39-af13-16ffdb3f7d89",
    "createdAtUtc": "2023-08-27T12:26:40.04",
    "status": "IN_PROGRESS",
    "payoutRequestDetails": {
        "destination": {
            "type": "BENEFICIARY",
            "id": "B12006NTH6",
            "iban": null,
            "accountNumber": null,
            "sortCode": null,
            "name": null,
            "address": null,
            "birthdate": null,
            "emailAddress": null,
            "phoneNumber": null,
            "bic": null
        },
        "currency": "GBP",
        "amount": 50,
        "reference": "230827141600320ZkO",
        "paymentDate": null
    }
}

Failure Response

Error codes:

Error CodeMeaning
4xxclient errors including bad request, authentication/authorisation, configuration or payment status problems
5xxserver errors

Body:

{
    "statusCode": 500,
    "statusName": "INTERNAL_SERVER_ERROR",
    "errorMessage": "Virtual Account Provider API Error -> ClientError code=INSUFFICIENT_BALANCE, message=Source does not have enough balance",
    "traceId": "32671492024036f8",
    "source": "MODULR"
}

Example Curl

curl --location 'https://api.sandbox.volumepay.io/api/merchants/{merchant-id}/applications/{application-id}/payouts' \
--header 'x-application-id: application-id' \
--header 'x-application-secret: aplication-secret' \
--header 'Content-Type: application/json' \
--data '{
    "payInAccountId": 1,
    "amount": 50
}'

With following values replaced by specific to your configuration:

  • merchant-id
  • application-id
  • applicaiton-secret

Payout status

Payout status is delivered to you by Payout Webhooks.

Payout status has following values:

StatusDescription
IN_PROGRESSpayment is in progress
PROCESSEDpayment is was processed (does not mean delivered)
CANCELLEDpayment was cancelled
FAILEDpayment failed
HELDpayment is held and may require contact with volume for it's further processing
RETURNEDpayment was delivered to destination account and returned

Payout status delivered by Webhooks

Once you've configured Payout Webhook URL in Merchant Portal, in Settings->Webhooks And Callbacks section, you will be provided with a Webhook for any status change. It is important to note that Webhooks are delivered in the same way regardless of where you have initiated Payout payment : in Merchant Portal or from the API.

Delivery guarantee

Each webhook is delivered until your endpoint answers with 200 OK.

Body

Webhook calls will be delivered as PUT REST call with following payload

{
  "eventTimeUtc" : "2023-08-27T12:26:42.725871Z",
  "payoutId" : "ea0d978b-6502-4d39-af13-16ffdb3f7d89",
  "payoutAmount" : 50.00,
  "payoutCurrency" : "GBP",
  "payoutReference" : "230827141600320ZkO",
  "payoutStatus" : "PROCESSED",
  "payoutWebhookDeliveryAttempt" : 0,
  "destination" : {
    "type" : "BENEFICIARY",
    "id" : "B12006NTH6"
  }
}

Implementation notes:

  • At this point we're not attaching full beneficiary details. Just it's id.

Important headers

Expect this call with following headers:

HeaderValue
Content-Typeapplication/json
Acceptapplication/json
Authorizationsignature of the request calculated as described here

Security

You can verify webhook integrity by checking it's signature passed via Authorization header. Mechanism is identical to signature verification if regular payment webhooks. You can find description here

How to get pay-in accounts list

In order to initiate Payout you need to use following payload, that requires payInAccountId.

{
    "payInAccountId": 10,
    "amount": 50
}

What payInAccountId is.

You can have several bank accounts configured in your Merchant Application. For example you can have one external bank account and one Virtual Account, or two Virtual Accounts. The point is that you can have one or more account in Merchant Application. In order for CreatePayout to know from which account you want to make the payment, you need to provide an id of such account, and this is payInAccountId.

How can I get one.

There is an endpoint GetPayInAccounts that privides you with such information.

Path

{{api-host}}/api/merchants/{{merchant-id}}/applications/{{application-id}}/payin-accounts?onlyVirtualAccounts=true

ParameterValueDescription
onlyVirtualAccountsbooleanreturn only virtual accounts

Headers

HeaderValue
x-app-nameapplication name
x-application-idapplication-id
x-application-secretapplication-secret
Acceptapplication/vnd.volume.v0.7+json

Authentication/Authorization

Endpoint is authentiated and authorized via application-id and applicaiton-secret values passed using x-application-id and x-application-secret headers mentioned above.

Response Body

[
    {
        "id": 1,
        "selector": "d71bae89-d70c-47f4-8be5-0a98ee70c6d9",
        "forCurrency": "GBP",
        "defaultForCurrency": null,
        "accountHolderName": "Volume",
        "accountIdentification1": {
            "type": "ACCOUNT_NUMBER",
            "number": "03330123"
        },
        "accountIdentification2": {
            "type": "SORT_CODE",
            "number": "000000"
        },
        "payeeAddress": {
            "streetName": null,
            "buildingNumber": null,
            "postCode": null,
            "townName": null,
            "country": "GB",
            "department": null,
            "subDepartment": null,
            "addressType": null
        },
        "isVirtualAccount": true,
        "virtualAccountInformation": {
            "balance": 269.00,
            "availableBalance": 269.00,
        }
    }
]