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 to failed

Due to returns, payouts and refunds can transition from a status of executed to failed. This means that your systems should be able to update a payout or refund from executed to failed 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:

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 payments view in Console, including a returned payment with attached tooltip.

The payments view in Console, including a returned payment with attached tooltip.

The payment lifecycle of a returned payment displayed in Console.

The payment lifecycle of a returned payment displayed in Console.

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.