Make a payout to an account that paid in
Learn about closed-loop payouts and how to make one.
In a closed-loop payout, a payment is made to a user who has made a previous payment (known as a closed-loop pay-in) to your merchant account.
We identify the user who made the pay-in using two types of id
:
payment_source_id
, which identifies a set of bank account details- user
id
, which identifies a specific user.
These id
values ensure that the payout is sent to the correct account, and that you don't need to collect additional user details.
Closed-loop payouts are useful if your customers need to top up and withdraw from an account frequently: for example, in gaming or investment apps.
Closed-loop payout overview
To make a closed-loop payout successfully, there are three key steps:
- Authentication: Properly configure authentication before your request. Most of this is the same as the initial pay-in request.
- Request configuration: Provide information including the value of the payout, the merchant account that the payout is made from and the payment source that the payout is made to.
See examples of a complete request. - Monitoring: Set up webhooks for your payout so that you can track its progress and confirm its success.
1. Authenticate a closed-loop payout
Before you can make a closed-loop payout, you must secure your requests. Authentication has two parts:
- Generating an access token
- Signing your requests and including an idempotency key.
1.1 Generate an access_token
access_token
You must include an access_token
with the payments
scope in order to make a closed-loop payout. This should be included as a Bearer header along with your payout request.
1.2 Include headers for signing and idempotency
All requests to the Payments API v3, including payouts, should include a valid Tl-Signature
header for security purposes. Learn how to sign your requests.
To help with payment signatures, try our signing libraries.
It's also important that your requests include a valid Idempotency-Key
header. This should be a unique key, and enables you to run your request multiple times without duplicating it.
2. Configure a closed-loop payout
After setting up your access_token
and headers, you're ready to configure and create your closed-loop payout request.
To create a payout, send a POST request to the /payouts
endpoint. You must include information about the payment and the beneficiary in this request (see below for the exact details to include, and .
2.1 Request parameters
The table below contains an overview of all the parameters you need to specify to initiate a closed-loop payout.
Parameter | Description |
---|---|
merchant_account_id | The unique id of the merchant account to make the closed-loop payout from. |
amount_in_minor | The value of the payout in a minor denomination. A minor denomination is the smallest unit of the specified currency, so 1 is equivalent to a penny or a cent. |
currency | The currency of the payment, provided as a 3-letter ISO 4217 currency code. For example, GBP for pounds, or EUR for euros.Note that you cannot make a payment from an account with one currency to an account with a different currency. For example from a GBP to EUR account. Ensure you provide the correct merchant_account_id . |
beneficiary.type | This must be set to payment_source for a closed-loop payout. |
beneficiary.reference | A reference for the payout. The beneficiary sees this reference alongside this payout on their statement or in their banking app. |
beneficiary.payment_source_id | The id of the account that made the original deposit.You can find this id as the value of payment_source.id in the payment_settled webhook for the initial pay-in.Alternatively, you can find this under Payment Source ID if you click the initial pay-in in the Console payments view. |
beneficiary.user_id | The id of the user that made the original deposit.You can find this id as the value of user_id in the payment_settled webhook for the initial pay-in.Alternatively, you can find this under User ID if you click the initial pay-in in the Console payments view. |
submerchants | This parameter is only relevant if you are a partner of TrueLayer operating as a collecting PSP. As a partner, you are considered a merchant, and any businesses you process transactions for are 'submerchants'. If this applies to you, you must provide the details of the submerchant you're processing a transaction for in this parameter, along with the user information for the payout. Find more information about the required information in the submerchants.ultimate_counterparty parameter in the API reference for the /v3/payouts endpoint. |
metadata | This parameter is optional. You can include up to 10 key-value pairs in this object. This is typically used to attach metadata that helps with reconciliation to your payout. |
2.2 Merchant account id
id
Include the id
of the merchant account you want to pay out from in your request. You include it in the merchant_account_id
parameter. This ensures that you make the payout from the correct account.
The merchant account that you use must be in the same currency as the payout and beneficiary account. (For example, you cannot make a EUR payment from a GBP merchant account or to a GBP beneficiary account.)
You can get your merchant_account_id
through two methods:
- Use the
/v3/merchant-accounts
endpoint to return a list of your merchant accounts. - Open the Merchant Account page in Console, select the correct currency, then open the Details tab.
This is an example of the merchant_account_id
parameter filled out correctly:
{
"merchant_account_id": "2a485b0a-a29c-4aa2-bcef-b34d0f6f8d51",
//...
}
2.3 Payout amount and currency
When you make a closed-loop payout, you must specify the value and currency of the payout.
To specify the value of your payout, provide a numeric value for the amount_in_minor
parameter. This should be the value of your payout in a minor denomination. This means that a value of 1500 corresponds to 15 GBP or 15 EUR.
To specify the currency of your payout, you need to provide a three-letter ISO 4217 currency code for the currency
parameter. For example, GBP for pounds sterling and EUR for euros.
This an example of the amount_in_minor
and currency
parameters filled out correctly:
{
//...
"amount_in_minor": 1500,
"currency": "EUR",
//...
}
2.4 Payout scheme selection
You can select a payment scheme for your payout to be made through by using the scheme_selection
object. Unlike creating a payment, the scheme_selection
object for payouts is not contained within a provider selection object.
There are three different values that you can set for scheme selection:
instant_preferred
instant_preferred
Preferably selects a payment scheme that supports instant payments for the currency and geography that you're making the payment in. However, the API will fall back to a non-instant scheme if instant payments are unavailable. The payout_executed
webhook will specify the actual scheme used. This is optimal when you don't mind if a payout settles slowly, and can help you avoid fees and payment failures.
instant_only
instant_only
Automatically selects a payment scheme that supports instant payments for the currency and geography that you're making the payment in. This type is optimal when you need payments to settle quickly.
preselected
preselected
Selects a payment scheme compatible with the currency of the payment and geographic region that you are paying in. This is useful if you want full control over the scheme that a payout goes through.
When you use a preselected scheme, you need to specify a scheme_id
. Ensure that your scheme ID is compatible with the currency that you are using. Possible scheme IDs are:
faster_payments_service
sepa_credit_transfer_instant
sepa_credit_transfer
See this code block for examples of each scheme selection type.
{
//[...]
"scheme_selection": {
"type" : "instant_preferred"
}
}
{
//[...]
"scheme_selection": {
"type" : "instant_only"
}
}
{
//[...]
"scheme_selection": {
"type" : "preselected",
"scheme_id": "polish_domestic_express"
}
}
We recommend using instant_preferred
for the best user experience.
<€100,000 payouts
Payouts that are €100,000 or over automatically move through the SEPA Credit scheme.
The payout appears as normal in the payments view on Console, with SEPA Credit displayed as the scheme.
Do not use the instant_only
scheme selection object for these payments, as they will immediately fail.
2.5 Beneficiary details
Use the beneficiary object in your payout creation request to specify who will receive your payout. For a closed-loop payout, it contains four mandatory parameters, explained below.
2.5.1 type
parameter
type
parameterIn a closed-loop payout, you must use a value of payment_source
for the type
parameter.
You can use a value of external_account
or business_account
for an open-loop payout or payout to your business account respectively.
2.5.2 payment_source
parameter
payment_source
parameterYou must provide the payment source id
if the initial pay-in in your payout request. You include this in the payment_source_id
parameter. This ensures the payout is sent back to the same account as the initial pay-in.
There are three ways to get the payment source id
for a previous pay-in:
- Check the value returned in the
payment_settled
webhook for the initial pay-in. - Make a request to the
/v3/payments/{id}
endpoint, using theid
of initial pay-in as a path parameter. - Click on the initial pay-in in the payment view in Console, and check the Payment Source ID in the right panel.
2.5.3 user_id
parameter
user_id
parameterWithin your payout request, you must include the user id
for the user who made the initial pay-in. You include this in the user_id
parameter.
When you accept the initial payment, you can provide a user id
or allow us to auto-generate one for you. Whichever approach you choose, you should use this id
to represent the same user for future payments.
As with the payment source, there are three ways to get the user id
for a previous pay-in:
- Check the value returned in the
payment_settled
webhook for the initial pay-in. - Make a request to the
/v3/payments/{id}
endpoint, using theid
of initial pay-in as a path parameter. - Click on the initial pay-in in the payment view in Console, and check the User ID in the right panel.
2.5.4 reference
parameter
reference
parameterInclude a string for the reference
parameter in your payout. The beneficiary sees this reference alongside this payout on their statement or in their banking app.
A common approach to take is to programmatically generate a reference that starts with a string that identifies your business. Then attach a number to it, which you and your user can use to reconcile the payout if needed.
We recommend that you keep your reference under 18 characters, and include no special characters other than -
or .
.
beneficiary
object example
beneficiary
object exampleThis an example of the beneficiary
object filled out correctly, including the four parameters described above:
{
//...
"beneficiary": {
"type": "payment_source",
"user_id": "f61c0ec7-0f83-414e-8e5f-aace86e0ed35",
"payment_source_id": "c7022d14-3f53-4162-99de-1d615b77b960",
"reference": "FinancialCo-738940"
}
}
2.6 Optional metadata
You don't need to include any values for the metadata
object. However, if your business needs to include extra metadata in your payout requests (for internal processes for example), you can choose to include up to 10 key value pairs.
To include metadata, provide key value pairs in the object in the format below:
{
//...
"metadata": {
"Metadata_key_1": "Metadata_value_1",
"Metadata_key_2": "Metadata_value_2",
"Metadata_key_3": "Metadata_value_3"
}
}
Example closed-loop payout requests
When you specify the mandatory parameters above in your request, you can initiate a closed-loop payout.
The code block below contains two examples of sandbox closed-loop payouts. One for a GBP payout, and one for a EUR payout.
POST /v3/payouts HTTP/1.1
Content-Type: application/json
Idempotency-Key: {RANDOM_UUID}
Tl-Signature: {SIGNATURE}
Authorization: Bearer {ACCESS_TOKEN}
Host: api.truelayer-sandbox.com
{
"merchant_account_id": "200552da-13da-43c5-a9ba-04ee1502ac57",
"amount_in_minor": 1,
"currency": "GBP",
"beneficiary": {
"type": "payment_source",
"user_id": "f61c0ec7-0f83-414e-8e5f-aace86e0ed35",
"payment_source_id": "41b500a4-5379-4f30-9eda-2fcc032afc37",
"reference": "PayOutRef"
}
}
POST /v3/payouts HTTP/1.1
Content-Type: application/json
Idempotency-Key: {RANDOM_UUID}
Tl-Signature: {SIGNATURE}
Authorization: Bearer {ACCESS_TOKEN}
Host: api.truelayer-sandbox.com
{
"merchant_account_id": "2a485b0a-a29c-4aa2-bcef-b34d0f6f8d51",
"amount_in_minor": 1,
"currency": "EUR",
"beneficiary": {
"type": "payment_source",
"user_id": "f61c0ec7-0f83-414e-8e5f-aace86e0ed35",
"payment_source_id": "c7022d14-3f53-4162-99de-1d615b77b960",
"reference": "PayOutRef"
}
}
3. Monitor a closed-loop payout
When you make a successful closed-loop payout, you receive a webhook and also a response that contains the payout id
only:
{
"id": "a7e4e74f-d7da-43e2-af7f-f953724a461c"
}
You can use this webhook and id
to confirm whether your payout was successful, and take action if appropriate.
There are two ways that you can check on the progress of your payout:
- Check the webhooks sent to your webhook URI you have set in Console (recommended)
- Make a GET request to the
/v3/payouts/{id}
endpoint.
Payout webhooks
We recommend that you use webhooks to monitor all of the requests you make with the Payments API v3. You receive webhooks for all events in your integration.
Webhooks for your integration are sent to the Webhook URI you have set on the Payments > Settings page in Console. You should ensure that you have set up request signing for your webhook notifications to ensure that they are reliable.
The two webhooks that you receive for a closed-loop payout are payout_executed
or payout_failed
. You should receive these very soon after your payout request.
This code block contains examples of an executed and a failed webhook for a closed-loop payout:
{
"type": "payout_executed",
"event_id": "8712ba64-7864-58d5-1ae8-20ddf61f47d8",
"event_version": 1,
"payout_id": "45784e7e-e592-4d9c-b315-3b6a203584bb",
"executed_at": "2024-01-12T13:53:49.963Z",
"beneficiary": {
"type": "payment_source",
"user_id": "f61c0ec7-0f83-414e-8e5f-aace86e0ed35",
"payment_source_id": "c7022d14-3f53-4162-99de-1d615b77b960"
},
"scheme_id": "internal_transfer"
}
{
"type": "payout_failed",
"event_id": "24089aed-4fd4-9e13-f8b2-f458f30c836c",
"event_version": 1,
"payout_id": "0a495e9f-2f41-4669-ba33-85407c0b26cb",
"failed_at": "2024-01-12T14:56:05.117850644Z",
"failure_reason": "insufficient_funds",
"beneficiary": {
"type": "payment_source",
"user_id": "f61c0ec7-0f83-414e-8e5f-aace86e0ed35",
"payment_source_id": "c7022d14-3f53-4162-99de-1d615b77b960"
}
}
GET payouts endpoint
You can use the payout id
you received when you made the payout to get information about it.
To do so, make a POST request to the /v3/payouts/{id}
endpoint, including the payout id
as a path parameter.
You receive a response in this format:
{
"id": "0cd1b0f7-71bc-4d24-b209-95259dadcc20",
"merchant_account_id": "AB8FA060-3F1B-4AE8-9692-4AA3131020D0",
"amount_in_minor": 0,
"currency": "GBP",
"beneficiary": {
"type": "external_account",
"reference": "string",
"account_holder_name": "string",
"account_identifiers": [
{
"type": "sort_code_account_number",
"sort_code": 560029,
"account_number": 26207729
},
{
"type": "iban",
"iban": "GB32CLRB04066800012315"
}
]
},
"metadata": {
"prop1": "value1",
"prop2": "value2"
},
"scheme_id": "faster_payments_service",
"status": "pending",
"created_at": "string"
}
The status
object is especially important, as you can use this to check the progression of your payout through its lifecycle.
Payout lifecycle
After you create a payout, it moves through a series of different states depending on its outcome. See the full list of payout statuses for more information.
This is the journey a payout usually follows:
- When a payout is first made, it has a status of
pending
before it's sent to the payment scheme for authorisation. - Once the payout has been authorised and is with the payment scheme for execution, it has a status of
authorized
.
It usually takes just a few seconds for a successful payout to transition through thepending
andauthorized
statuses. - Depending on whether the payment scheme executes the payout, it transitions to one of two statuses:
payout_executed
if the payout was successful.payout_failed
if the payout could not be executed.
If a payout fails, the webhook contains a failure_reason
that you can use to identify why it failed.
A return occurs when a payout or refund is executed, but rejected by a banking provider. The money moves back to your merchant account.
The reasons for a return are decided by banks. For example, a payout might be returned if the account you're attempting to pay has been closed by the bank or is under investigation.
This occurs in less than 0.01% of payouts, but you must make sure that your system can handle a payout transitioning from executed
to failed
.
Updated about 2 months ago