Payout and refund returns
Handle payouts that are returned by a banking provider.
You may need to opt in to this behaviour
If you created your Console account after 15 April 2024, payout and refund returns are automatically enabled.
If you created your account before 15 April 2024, you need to contact us to enable this behaviour, and may need to change how your integration handles payouts and refunds if you do so.
Usually a payout or refund transitions through statuses in the order: pending
> authorized
> executed
/failed
. As such, we refer to executed
and failed
as the terminal statuses for a payout or refund. This is different to a pay-in to your merchant account, where the terminal status is settled
or failed
.
When a payout or refund is returned, it transitions back from a status of executed
to failed
.
Payouts and refunds can transition from
executed
tofailed
Due to returns, payouts and refunds can transition from a status of
executed
tofailed
. This means that your systems should be able to update a payout or refund fromexecuted
tofailed
if you receive a webhook informing you of this status change.Historically, we considered
executed
to be a terminal status, and although it stills remains the terminal status in most cases, you must ensure your integration can handle this unlikely scenario.
Why do returns happen?
A return occurs when a previously executed payout or refund is rejected by the beneficiary's banking provider. As such, the money is sent back to your merchant account.
The reasons for a return are decided by banks, but one example is if the account you're attempting to pay has been closed by the bank or is inaccessible as it's under investigation.
How to identify a return
You can identify a return in four ways:
Webhooks received for returns
When a payout or refund is executed, you receive the payout_executed
or refund_executed
webhook.
If a payout or refund is returned and transitions from executed
to failed
, you receive the payout_failed
or refund_failed
webhook. This failed webhook has a failure_reason
of returned
.
This is an example of the webhook you receive when a payout fails when it's returned:
{
"type": "payout_failed",
"event_id": "db243210-4ced-7904-2e14-7cf70887dbd8",
"event_version": 1,
"payout_id": "5175eb89-d10b-4867-a50b-b2f24ba4ee4d",
"failed_at": "2023-11-29T13:57:14.093Z",
"failure_reason": "returned",
"failure_details": {
"message": "Payment returned by the beneficiary bank.",
"is_transient": false
},
"beneficiary": {
"type": "external_account"
},
"metadata": {
"obps": "true",
"prop1": "value1",
"prop2": "value2"
}
}
The GET payout or refund endpoints
If a payout or refund fails with a reason of returned, you can also confirm this by using endpoints:
- To check a payout, make a GET request to the
/v3/payouts/{id}
endpoint, substituting the path parameter with theid
of the payout. - To check a refund, make a GET request to the
/v3/payments/{payment_id}/refunds/{refund_id}
endpoint, substituting the path parameters with theid
of the payment and refund.
The response you receive is mostly the same as the webhook you receive. Along with other information, it contains both status
and failure_reason
objects you can use to check if the payout or refund was returned.
The Console payments view
If a payout or refund is returned, it transitions from a status of Executed to Failed in the Console payments view. When you select that payment, the failure reason in the Payment Lifecycle window shows as Returned
.
Returned payouts which arrive in your merchant account are indicated with a tooltip.
The GET transactions and refunds endpoints
If a payout is returned, you see the payout as failed
if you make a request to the /v3/payments/{id}/refunds/{id}
endpoint.
However, you continue to see the payout as executed
when you make a request to the /v3/merchant-accounts/{id}/transactions
endpoint. There is a corresponding external payment id
which identifies a return for the payout. The return_for
object tracks these.
Below is an example of this object within an external payment. The returned_id
in the object is the id
of the original returned payout.
{
"type": "external_payment",
"id": "f1a36337-4c22-42e7-bc30-0599a32aa6e9",
"currency": "GBP",
"amount_in_minor": 1,
"status": "settled",
"settled_at": "2024-03-15T17:22:05.450Z",
"remitter": {
"account_holder_name": "xxx",
"account_identifier": {
"type": "sort_code_account_number",
"sort_code": "040075",
"account_number": "58114637"
},
"account_identifiers": [
{
"type": "sort_code_account_number",
"sort_code": "010101",
"account_number": "12345678"
},
{
"type": "iban",
"iban": "GB24BARC20038498146991"
}
],
"reference": "xxx"
},
"return_for": {
"type": "identified",
"returned_id": "9e07d528-b285-4875-b26a-ef6644d804aa"
}
},
{
"type": "payout",
"id": "{placeholder-id}",
"currency": "GBP",
"amount_in_minor": 1,
"status": "executed",
"created_at": "2024-03-15T17:04:02.561706613Z",
"executed_at": "2024-03-15T17:04:05.763Z",
"beneficiary": {
"type": "external_account",
"account_holder_name": "xxx",
"account_identifier": {
"type": "sort_code_account_number",
"sort_code": "010101",
"account_number": "12345678"
},
"account_identifiers": [
{
"type": "sort_code_account_number",
"sort_code": "010101",
"account_number": "12345678"
},
{
"type": "iban",
"iban": "GB24BARC20038498146991"
}
],
"reference": "xxx"
},
"context_code": "withdrawal",
"payout_id": "9e07d528-b285-4875-b26a-ef6644d804aa",
"returned_by": "f1a36337-4c22-42e7-bc30-0599a32aa6e9"
}
[
{
"id":"5000fc14-a6ec-47d2-9ed0-4a1511d3c113",
"amount_in_minor":10,
"currency":"GBP",
"status":"failed",
"reference":"TUOYAP",
"created_at":"2024-03-08T12:03:19.066995832Z",
"authorized_at":"2024-03-08T12:03:19.511051580Z",
"executed_at":"2024-03-08T12:03:20.457Z",
"failed_at":"2024-03-08T12:03:22.447Z",
"failure_reason":"returned"
}
]
Rarely, the return_for
object has a value of unknown
. If this happens, you need to identify the corresponding return manually (for example, using the date, time and IBAN).
Handling returns
Returns are rare, accounting for a negligible amount of initiated payouts and refunds. Even so, your integration should be able to handle returns to ensure a good user experience.
Handling returns is different from other failure reasons, because a returned payout or refund may cause payouts and refunds to transition from the executed
status to failed
status.
Your integration may contain a database of payouts and refunds, and the id
for each of them. Your integration should be able to programmatically update the status of existing payouts and refunds from executed
to failed
when you receive a webhook indicating that transition.
Test payout returns in sandbox
To test payout returns, create a sandbox payout that will automatically return to your merchant account when it settles.
Send a payout creation request with the string TUOYAP
in the reference
field. Ensure that you are paying out from your sandbox merchant account.
This is an example of the request, with the reference:
{
"merchant_account_id": "Your_merchant_account_id",
"amount_in_minor": 10,
"currency": "EUR",
"beneficiary":{
"type": "external_account",
"account_holder_name": "TRUELAYER LTD",
"account_identifier": {
"type": "iban",
"iban": "GB75CLRB04066800000871"
},
"reference": "TUOYAP"
},
"metadata": {
"Metadata_key_1": "Metadata_value_1"
}
}
The status of the payout transitions from executed
to failed
, and you receive webhooks for both statuses.
Updated 5 months ago