---
updatedAt: 2026-09-30T15:54:20.000Z
---

Fetch the complete documentation index at: https://docs.avvio.xyz/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# Send a payout link

Create a single-use link on your server, send its URL to the recipient, and
the payout goes out once they enter their own bank details on the page Avvio
hosts. [Payout links](/products/payout-links/) explains when to use one.

1. Create the link with `POST /payments/organizations/{orgId}/payout-links`.
2. Send its `url`. The hosted page calls `GET /payout-links/{token}` and
   `POST /payout-links/{token}/submit` for you.
3. Check the outcome from the `payout.*` webhooks or `GET .../orders`.

## 1. Create a payout link

Call `POST /payments/organizations/{orgId}/payout-links` from your backend.

To try it from the API reference, authorize with an `avvio_test_*` key (never a
live one), enter your normal `orgId`, set a new `Idempotency-Key`, fill in the
body and click **Try It**. Open the response's `url` to play the recipient.

```http
POST /payments/organizations/{orgId}/payout-links
x-api-key: avvio_live_…
Idempotency-Key: 3f81e921-bc01-447a-9a11-0982716a5b42
Content-Type: application/json

{
  "amount": "75.00",
  "destinationCurrency": "MXN",
  "endUserId": "customer_42",
  "endUser": { "name": "Jesse Alvarez", "email": "jesse@example.com" },
  "reference": "ZZ-WAGE-1",
  "expiresInMinutes": 60
}
```

`amount` is a USD decimal string with at most two fractional digits.
`destinationCurrency` is a three-letter ISO 4217 code for a currency your
organization can pay out to. For USD, the hosted form asks for the fields of
the USD corridor your organization is served: a domestic US account or an
international bank's SWIFT details, depending on its routing (read
`GET /recipients/{orgId}/corridors`). `endUserId` is required,
1–128 characters. `reference` is optional, 1–128 characters of letters, digits,
spaces and `. _ : -`. `expiresInMinutes` is a whole number from 1 to 10,080; it
defaults to 60. Any other top-level field is refused with `400 VALIDATION_ERROR`.

The link's `amount` is what the recipient receives, in USD value: the fee is
added on top, so your balance is debited `amount` plus the fee.

`endUser.name` and `endUser.email` describe your end user, the person sending the
money, not the account holder being paid. Send `endUser.email` when you have it.
The regulatory receipt is emailed to the sender, and a link created without an
address makes the hosted page ask for one before it will pay.

Response:

```json
{
  "payoutLinkId": "cmf3k2xh10010q8b7a3c5e7gj",
  "url": "https://pay.avvio.xyz/l/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresAt": "2026-08-17T05:28:39.569Z",
  "status": "pending"
}
```

Your `Idempotency-Key` is stored on the link record. Creating the link twice, or
a recipient submitting several times, cannot produce two payouts.

## 2. Send the URL, and let the page do the rest

Send `url` to the recipient by SMS, email or your app. The hosted page then
calls two public endpoints with the single-use signed token from
the URL:

```http
GET  /payout-links/{token}
POST /payout-links/{token}/submit
```

Send no `x-api-key` or `Idempotency-Key` to these routes. The short-lived
token in the path is the credential. It is a signed JWT: it cannot be forged,
but its payload (the link id and your organization id) is readable by anyone
who has the URL. Don't decode, construct or log it.

The read returns what the page renders:

- `sender.name`, your organization's display name, so the recipient recognizes
  the payment. The response body carries no organization id.
- The amount and the fields the corridor needs.
- The end user's saved accounts, masked to name and last 4.
- A `preview` of the rate, the fee and roughly what lands.
- `paymentReasons` and `defaultPaymentReason` for corridors that need a purpose,
  and the Regulation E `disclosure`.

The preview has the same shape as `GET /rates` and is indicative for the same
reason: the binding quote is priced at submit, against the bank details the page
collects. Because the link pays exact output, `destinationAmount` is `amount` ×
`rate` and `totalDebit` is `amount` + `fee`. When the corridor can't be priced in advance, `preview` is omitted and
the link is still payable.

```json
{
  "sender": { "name": "Northstar Logistics" },
  "amount": { "currency": "USD", "amount": "75.00" },
  "destinationCurrency": "MXN",
  "reference": "ZZ-WAGE-1",
  "expiresAt": "2026-08-17T05:28:39.569Z",
  "status": "pending",
  "requirements": [ { "id": "clabeNumber", "title": "CLABE", "pattern": "^[0-9]{18}$", "required": true } ],
  "savedDestinations": [ { "destinationAccountId": "sbx_acct_MXN_4471_ae66cbc5", "name": "Ana Ruiz", "label": "BBVA México", "last4": "4471" } ],
  "preview": {
    "indicative": true,
    "sourceAmount": { "currency": "USD", "amount": "75.00" },
    "destinationAmount": { "currency": "MXN", "amount": "1275.75" },
    "fee": { "currency": "USD", "amount": "0.38" },
    "totalDebit": { "currency": "USD", "amount": "75.38" },
    "rate": "17.010050924685068"
  }
}
```

Submit exactly one of these bodies:

```json
{ "esignConsent": true, "name": "María González", "email": "maria@example.com", "details": { "clabeNumber": "012180000080004471" } }
```

```json
{ "esignConsent": true, "destinationAccountId": "sbx_acct_MXN_4471_ae66cbc5" }
```

For a new destination, `name` must be non-empty, `email` valid, and `details`
keyed by the field ids from the read. Don't mix a saved `destinationAccountId`
with new-destination fields.

`esignConsent` is required on both and must be `true`. The page shows it as an
unticked checkbox: the sender's consent to receive their receipt electronically
under the E-Sign Act. A submit without it is `400 VALIDATION_ERROR`, and the
link stays spendable.

Either body may also carry `senderEmail`, the sender's own address, for links
created without `endUserEmail`. With neither, the submit is refused with
`400 VALIDATION_ERROR` (`senderEmail: required`) before anything is registered
or paid, because the receipt could not be delivered.

### The receipt

A link payout is a remittance transfer under Regulation E, so the sender is
emailed a receipt on submit, and the submit response returns the same figures
as `disclosure` for the page to show. [Payout links](/products/payout-links/#a-regulatory-receipt-on-every-payout)
lists what the receipt contains.

### Token handling

- The body of `GET /payout-links/{token}` never carries your `orgId`, your `endUserId` or full bank details. The token itself carries the organization id, as above.
- A second submit returns the original payout with `status: "already_submitted"`, so a double tap on a flaky mobile connection pays once.
- A validation error, such as mistyped account digits, keeps the token active so the recipient can correct it. A payment-network error after the link has been claimed marks it `failed` for good; mint a new link.
- Reading an expired or tampered token returns the same `404 Not Found` in every case. A link that is being paid or has failed still reads, with that `status`.

## 3. Check the outcome

A submitted link becomes an ordinary payout, debited from your balance when it
is accepted. On a routing where you fund each payout yourself, it waits with
`requiresFunding: true` until you do ([Fund your balance](/funding/)). Follow it with the `payout.*` [webhooks](/webhooks/) or
`GET /payments/organizations/{orgId}/orders/{payoutId}`, as in
[Track status & failures](/status/). Link payouts count against the same caps
as any other payout ([Send a payout](/payouts/#caps-and-refusals)).
