Skip to content

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

    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": "[email protected]" },
    "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:

    {
    "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

    Section titled “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:

    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.

    {
    "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:

    { "esignConsent": true, "name": "María González", "email": "[email protected]", "details": { "clabeNumber": "012180000080004471" } }
    { "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.

    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 lists what the receipt contains.

    • 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. 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). Follow it with the payout.* webhooks or GET /payments/organizations/{orgId}/orders/{payoutId}, as in Track status & failures. Link payouts count against the same caps as any other payout (Send a payout).

Was this page helpful?