Make a payout to an external account

Learn about open-loop payouts and how to make one.

In an open-loop payout, a payment is made from a merchant account to an external account. There's no need for the external account to have previously made a payment via TrueLayer.

As the payment is made to an external account, it's important to collect the correct bank details for the payment. These are usually either the user's SCAN or IBAN, for UK or EU payouts respectively.

Open-loop payouts can be made to any number of external accounts, given you have funds and the bank details for your users.

Open-loop payouts can be made to any number of external accounts, given you have funds and the bank details for your users.

Open-loop payouts are a useful solution if you already have infrastructure for accepting payments, and want to integrate instant payouts to your users across the UK and Europe.

Open-loop payout overview

  1. Authentication: Properly configure authentication before your request. This means you need a suitable access_token and headers for your request.
  2. Request configuration: Provide information including the value of the payout, the bank details of the beneficiary of the payout and metadata.
  3. Monitoring: Set up webhooks for your payout so that you can track its progress and confirm its success.

1. Authenticate an open-loop payout

Before you can make an open-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

You must include an access_token with the payments scope in order to make an open-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 an open-loop payout

After setting up your access_token and headers, you're ready to configure and create your open-loop payout request.

To create a payout, send a POST request to the /payouts endpoint.

2.1 Request parameters

The table below contains an overview of all the parameters you need to specify to initiate an open-loop payout.

ParameterDescription
merchant_account_idThe unique id of the merchant account to make the open-loop payout from.
amount_in_minorThe 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.
currencyThe 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.typeThis must be set to external_account for an open-loop payout.
beneficiary.referenceA reference for the payout.

The beneficiary sees this reference alongside this payout on their statement or in their banking app.
beneficiary.account_holder_nameThe name of the person you're paying. You have to include this in your request.
beneficiary.date_of_birthThe date of birth of the person you're paying, in the format YYYY-MM-DD.
beneficiary.addressThe address of the person you're paying. This object contains the following parameters:

- address_line1
- address_line2
- city
- state
- zip
- country_code
beneficiary.account_identifier for SCAN payoutIf you're making a SCAN payout, specify sort_code_account_number in the account_identifier.type parameter.

You then need to provide the person you're paying's sort code and account number in the account_identifier.account_number and account_identifier.sort_code parameters respectively.
beneficiary.account_identifier for IBAN payoutIf you're making an IBAN payout, specify iban in the account_identifier.type parameter.
submerchantsThis 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.
metadataThis 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

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:

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 an open-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": "GBP",
//...
}

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

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

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

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 or instant_only for the best user experience.

2.5 Beneficiary details

Use the beneficiary object in your payout creation request to specify who will receive your payout. For an open-loop payout, it contains four mandatory parameters and two optional parameters, explained below.

2.5.1 type parameter

In an open-loop payout, you must use a value of external_account for the type parameter.

You can use a value of payment_source or business_account for a closed-loop payout or payout to your business account respectively.

2.5.2 account_holder_name parameter

In this parameter, specify the name of the person you're paying.

2.5.3 account_identifier parameter

This is an object that contains several parameters. The first is account_identifier.type, in which you use a value of sort_code_account_number or iban to specify a payment to SCAN or IBAN bank details respectively. The rest of the object varies based on your selection for type:

  • If you use sort_code_account_number, you need to provide this information in the account_identifier.sort_code and account_identifier.account_number parameters.
  • If you use iban, you need to provider this information in the account_identifier.iban parameter.

2.5.4 reference parameter

Include 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 ..

2.5.5 Optional user information parameters

The following two parameters are considered optional by the API, but are required if a client's transactions are subject to sanction screening. We recommend you always include the user's address and date of birth if possible to reduce the chance of RFIs.

The first optional parameter is beneficiary.address. This is an object that contains the following six fields to describe the address, which you provide strings for:

  • address_line1: Description of the street address and house number, between 1 and 50 characters.
  • address_line2: Optional in an address, this is for extra details like building name or apartment number, between 1 and 50 characters.
  • city: Name of the city or locality, between 1 and 50 characters.
  • state: Name of the county, province or state, between 1 and 50 characters.
  • zip: ZIP or postal code, between 1 and 20 characters.
  • country_code: The two-letter country code, in the ISO 3166-1 alpha-2 format.

The second optional parameter is beneficiary.date_of_birth. For this parameter, provide a date in the format of YYYY-MM-DD.

📘

User details for businesses

If you are making a payment to a business using the external_account method for the beneficiary, provide the business name, business founding date and business address.

Beneficiary object example

This an example of the beneficiary object filled out correctly, including the six parameters described above:

{
//...
  "beneficiary": {
		"type": "external_account",
		"account_holder_name": "JOHN SANDBRIDGE",
		"account_identifier": {
			"type": "sort_code_account_number",
			"sort_code": "040668",
			"account_number": "00000871"
		},
		"reference": "TestCo-627364",
		"address": {
			"address_line1": "40 Finsbury Square",
			"state": "London",
			"city": "London",
			"country_code": "GB",
			"zip": "EC2A 1AE"
		},
		"date_of_birth": "1990-01-31"
	}
}
{
//...
  "beneficiary": {
		"type": "external_account",
		"account_holder_name": "JOHN SANDBRIDGE",
		"account_identifier": {
			"type": "iban",
			"iban": "GB75CLRB04066800000871"
		},
		"reference": "TestCo-627364",
		"address": {
			"address_line1": "40 Finsbury Square",
			"state": "London",
			"city": "London",
			"country_code": "GB",
			"zip": "EC2A 1AE"
		},
		"date_of_birth": "1990-01-31"
	}
}

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 open-loop payout requests

Specify the mandatory parameters above in your request to initiate an open-loop payout.

The code block below contains two examples of sandbox open-loop payouts: one for a SCAN payout, and one for a IBAN payout.

Note that the IBAN payout uses a UK IBAN. In sandbox, you must use a UK IBAN to make payouts.

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": "GBP",
	"beneficiary": {
		"type": "external_account",
		"account_holder_name": "JOHN SANDBRIDGE",
		"account_identifier": {
			"type": "sort_code_account_number",
			"sort_code": "040668",
			"account_number": "00000871"
		},
		"reference": "TestCo-627364",
		"address": {
			"address_line1": "40 Finsbury Square",
			"state": "London",
			"city": "London",
			"country_code": "GB",
			"zip": "EC2A 1AE"
		},
		"date_of_birth": "1990-01-31"
	}
}
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": "GBP",
	"beneficiary": {
		"type": "external_account",
		"account_holder_name": "JOHN SANDBRIDGE",
		"account_identifier": {
			"type": "iban",
			"iban": "GB75CLRB04066800000871"
		},
		"reference": "TestCo-627364",
		"address": {
			"address_line1": "40 Finsbury Square",
			"state": "London",
			"city": "London",
			"country_code": "GB",
			"zip": "EC2A 1AE"
		},
		"date_of_birth": "1990-01-31"
	}
}

3. Monitor an open-loop payout

When you make a successful open-loop payout, you receive a response that contains the payout id only:

{
	"id": "a7e4e74f-d7da-43e2-af7f-f953724a461c"
}

You also receive a webhook when you make the payout, which contains extra information. (See below for more.)

You can use the id and webhook you receive to confirm whether your payout was successful, and take action if needed.

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 and not from external sources.

The two webhooks that you receive for a open-loop payout are payout_executed or payout_failed. You should receive these very soon after your payout request.

This is an example of a payout_executed webhook for an open-loop payout:

{
  "type": "payout_executed",
  "event_id": "66eb019a-f103-4c0f-ae2e-c4a9f3bcb82d",
  "event_version": 1,
  "payout_id": "796cab79-cd1a-4e01-8241-93ac28bf4260",
  "executed_at": "2024-01-17T12:09:54.117Z",
  "beneficiary": {
    "type": "external_account"
  },
  "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": "external_account"
  },
}

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
      },
    ]
  },
  "metadata": {
    "prop1": "value1",
    "prop2": "value2"
  },
  "scheme_id": "faster_payments_service",
  "status": "pending",
  "created_at": "string"
}
{
  "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": "iban",
        "iban": "GB32CLRB04066800012315"
      }
    ]
  },
  "metadata": {
    "prop1": "value1",
    "prop2": "value2"
  },
  "scheme_id": "faster_payments_service",
  "status": "pending",
  "created_at": "string"
}

Use the status object 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:

  1. When a payout is first made, it has a status of pending before it's sent to the payment scheme for authorisation.
  2. 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 the pending and authorized statuses.
  3. Depending on whether the payment scheme executes the payout, it transitions to one of two statuses:
    1. payout_executed if the payout was successful.
    2. 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, if the account you're attempting to pay has been closed by the bank, or is inaccessible as it's 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.

Low balance notifications

You can contact TrueLayer to enable an optional balance_notification webhook. This can help ensure that you always have sufficient funds in order to make payouts to external accounts when needed.

Learn more about low balance notifications and how to enable them.