PayDirect to Payments v3 API: payouts and refunds

Send money out of your merchant account, and refund a specific pay-in.

With Payments API V3, both types of payouts supported by PayDirect are still available:

  • closed-loop payouts: when the payout is sent to a user who already made a deposit into the merchant account.
  • open-loop payouts: when the payout is sent to a new, “unknown” bank account.

We've also introduced a new closed-loop payout API with v3:

  • Refunds: when you're refunding a specific payment into your merchant account, partially or fully. The amount refunded cannot be greater than the original pay-in.

Closed-loop payouts

PayDirect endpointPayments v3 endpoint
https://paydirect.truelayer.com/v1/users/withdrawalshttps://api.truelayer.com/payouts

To make a closed-loop payout in v3, you only need to specify a payment_source.id as the beneficiary. As in the payment_settled webhook, the payment_source.id is a unique ID TrueLayer creates that refers to the original bank account that the user deposited funds from. This ensures that we can send funds to the exact same bank account that was used to deposit.

This table shows which values in a PayDirect request correspond to which values in a Payments API v3 request.

PayDirect request objectPayments v3 request object equivalent
account_idbeneficiary.payment_source_id
user_idbeneficiary.user_id
beneficiary_referencebeneficiary.reference
transaction_idThis is not used in Payments v3
A merchant account ID is not required in PayDirectmerchant_account_id

Below is an example:

Below is an example request and response:

{
    "merchant_account_id": "01829515-3525-4812-a6c0-6d9e195c947e",
    "amount_in_minor": 1000,
    "currency": "GBP",
    "metadata":
    {
        "brand": "TL Casino"
    },
    "beneficiary":
    {
        "type": "payment_source",
        "user_id": "4d9af4be-f75b-4ca7-be9b-b842bf7237c1",
        "payment_source_id": "1c928e0e-6dc9-497c-8154-83dce43b2377",
        "reference": "Payout0001"
    }
}

Here:

  • beneficiary.payment_source_id is returned from the pay-in webhook (or GET status) once the payment has been settled.
  • beneficiary.user_id is the same user.id used in payment creation. It can also be returned from the pay-in webhook (or GET status) once the payment has been settled.
{
	"id": "5016c85d-de28-406d-9866-0a3ed85af3b1"
}

See our documentation for more about closed-loop payouts.

Refunds

If you are issuing refunds using PayDirect APIs, Payments v3 makes it a lot easier with a dedicated endpoint for refunds.

The Refunds API is similar to, but not the same as, closed-loop payouts. For refunds:

  • The maximum amount you can send is the amount of the pay-in being refunded.
  • The only information required for the Refund API request is the payment_id.
  • It is possible to issue multiple refunds for the same pay-in, as long as they don't exceed the original pay-in amount.

See our documentation for more about refunds.

PayDirect to closed-loop payouts and refunds

This diagram is meant to show:

  • On the left (grey underground), a PayDirect API payout (withdrawal) example.
  • On the center (blue underground), the equivalent of the Payout (withdrawal) API call in v3. Below is an example instead of the Refunds API (which as you can see is instead using the payment_id in the URI for reconciliation and refund up to the original amount).
  • On the right (black background), is an example of a Pay-In settled into the Merchant Account, the information incoming from it, and how those are used for then executing Payouts or Refunds.

Go to our Figma prototype to see a larger version of this image.

Open-loop payouts

Docs | API specs

PayDirect endpointPayments v3 endpoint
https://paydirect.truelayer.com/v1/users/withdrawalshttps://api.truelayer.com/payouts

In Payments v3, you can still also make payouts to any supported bank account by using the beneficiary type external_account and submitting the relevant account details. This is called an open-loop payout.

PayDirect request objectPayments v3 request object equivalent
beneficiary_ibanbeneficiary.account_identifier.iban
or
beneficiary.account_identifier.sort_code and beneficiary.account_identifier.account_number
beneficiary_namebeneficiary.account_holder_name
beneficiary_referencebeneficiary.reference
transaction_idThis is not used in Payments v3
A merchant account ID wasn't required in PayDirectmerchant_account_id

TrueLayer conducts sanction and AML screening on all transactions to and from your merchant account. To reduce the number of RFIs raised due to sanctions investigations, you must also include the user's date of birth and address in the API request.

Below is an example request and response for a v3 open-loop payout:

{
    "merchant_account_id": "c6ba13f2-35ef-4556-a99e-a6afd6275044",
    "amount_in_minor": 100,
    "currency": "GBP",
    "beneficiary":
    {
        "type": "external_account",
        "reference": "Withdrawal",
        "account_holder_name": "John Smith",
        "account_identifier":
        {
            "type": "sort_code_account_number",
            "sort_code": "<sort code>",
            "account_number": "<account number>"
        },
        "date_of_birth": "1992-08-03",
        "address":
        {
            "address_line1": "1 Hardwick St",
            "address_line2": "Clerkenwell",
            "city": "London",
            "state": "London",
            "zip": "EC1R 4RB",
            "country_code": "GB"
        }
    },
    "metadata":
    {
        "prop1": "value1",
        "prop2": "value2"
    }
}
{
	"id": "f5a6c87e-446a-4dcc-a91e-a74e3e4e0875"
}

Consume payouts webhooks and payout statuses

This diagram shows possible payout statuses:

StatusDescriptionNotification method
pendingThe payout has been created with TrueLayer's API. It has not yet been authorised and sent to the payment scheme for execution.Status only via
/GET /payouts/{payment_id}
authorizedThe payout has been created via TrueLayer's API and has been sent to the payment scheme for execution.Status only via
/GET /payouts/{payment_id}
payout_executedThe payout was executed. The payout amount has been deducted from your merchant account.Status via
/GET /payouts/{payment_id}

Webhook sent to your endpoint
payout_failedThe payout failed. The payout amount has not been deducted from your merchant account.Status via
/GET /payouts/{payment_id}

Webhook sent to your endpoint

🚧

We recommend that developers use our signing libraries to verify the Tl-Signature of the received webhooks.

Webhook URLs need to be added in the TrueLayer Console. See Setting up the TrueLayer Console.

Webhook examples:

{
    "type": "payout_executed",
    "event_id": "1b712c55-6bc4-487c-8a5b-2107623fe3bf",
    "event_version": 1,
    "payout_id": "5016c85d-de28-406d-9866-0a3ed85af3b1",
    "executed_at": "2022-09-14T16:39:50.593Z",
    "beneficiary":
    {
        "type": "payment_source",
        "user_id": "4d9af4be-f75b-4ca7-be9b-b842bf7237c1",
        "payment_source_id": "1c928e0e-6dc9-497c-8154-83dce43b2377"
    },
    "metadata":
    {
        "brand": "TL Casino"
    }
}
{
    "type": "payout_failed",
    "event_id": "f8a144dc-a550-4681-844b-4de231ec3764",
    "event_version": 1,
    "payout_id": "f8a144dc-a550-4681-844b-4de231ec3764",
    "failed_at": "2022-09-14T16:41:21.006718283Z",
    "failure_reason": "insufficient_funds",
    "beneficiary":
    {
        "type": "payment_source",
        "user_id": "4d9af4be-f75b-4ca7-be9b-b842bf7237c1",
        "payment_source_id": "1c928e0e-6dc9-497c-8154-83dce43b2377"
    },
    "metadata":
    {
        "brand": "TL Casino"
    }
}