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

# Checkout

Take card, bank and crypto payments from your own site with one hosted link.

Your server creates a checkout link, the buyer pays on a page we host, and a
signed webhook tells you when the money has arrived. Checkout uses the same API
key, webhook endpoint and event feed as payouts. Its contract is its own file,
[partner-checkout.openapi.yaml](/partner-checkout.openapi.yaml).

## How it works

1. **Set up** verification, card payments, an API key and a webhook endpoint
   ([Set up checkout](/checkout-setup/)).
2. **Create a product** with one fixed price, or put a one-off amount on the
   link itself.
3. **Publish a link** and redirect the buyer to its `shareUrl`.
4. **Fulfil** on `checkout_payment.paid`, joined to your order by
   `clientReferenceId`.
5. **Reverse** the fulfilment on `checkout_payment.refunded` or
   `checkout_payment.reversed`. A paid card payment can still be refunded or
   charged back.

## Payment methods

A link without `methods` takes cards, and the card page also offers Apple Pay,
Google Pay and PayPal. Pass `methods` to offer `bank` or `crypto`, each naming
the account or address to pay into.

Cards are processed by Whop and are in pilot, opening to verified businesses a
few at a time. Buyers see `WHOP*` and your business name on their statement.
Bank and stablecoin payments go live once business verification is approved.

## Where the money lands

`paid` means the acceptor has the money. Card payments collect in your card
balance with the processor, which you withdraw from **Checkout → Balance**. Bank
and crypto payments arrive in the account or address the link names.

Checkout payments never touch your Avvio ledger. They get no
`GET /balance_transactions` row, card money never appears in `GET /balance`,
and `settledAt` is always `null` in this version.

## Link states

A link's status is spelled `cancelled`, unlike a payout's `canceled`.

| `status`    | Meaning |
| ----------- | ------- |
| `draft`     | Not publicly readable. Edit it with `PATCH` or delete it |
| `sent`      | Live. `shareUrl` serves the page |
| `cancelled` | Paused. The page answers `410` and every rail behind it stops. Permanent in this version |
| `expired`   | Past its `expiresAt`. The page answers `410` from that instant, and the stored status follows within a minute. Terminal |

Publish a draft with `POST /links/{linkId}/publish`. A live link's price is
fixed, because a buyer may already be looking at it. The only allowed `PATCH`
sets `expiresAt` alone (a later instant, or `null`); anything else is a `400`.
To change the price, pause the link and create another. A link that has been
live cannot be deleted, and an `expired` one cannot be revived. API-made links
carry `source: "api"`, and `GET /links?source=api` (or `dashboard`) filters on
it.

## Refunds and disputes

Treat `paid` as provisional. Card payments can be refunded at any time and
charged back weeks later. Bank and stablecoin payments cannot be reversed at
the source; send the payer a payout to return one. Two changes raise no event.
An open dispute sets `disputeSubstatus` until it resolves. A bank deposit short
of the total stays `pending` with a `reviewReason` until someone accepts it in
the dashboard. See [Refund a payment](/refunds/).

## A link without code

Create and publish a link in the dashboard and paste `shareUrl` anywhere.
Appending `?client_reference_id=<value>` carries a per-buyer reference on the
card rail only, and only best effort. The hosted-fallback and 3DS-restore paths
drop it, and a bank payer types the link's slug instead. One link per order,
with its own `clientReferenceId`, is the join that works on every rail.

## Embedding

Embedding is not supported in this version, so redirect to `shareUrl` and
back. The payer page sends `X-Frame-Options: DENY` and
`Content-Security-Policy: frame-ancestors 'none'`. The card form is already the
processor's iframe, 3-D Secure and Apple Pay need the top window, browsers
partition framed storage, and with no origin allowlist a framed card page
invites clickjacking.

## If you know Stripe

| Stripe | Here |
| --- | --- |
| `POST /v1/checkout/sessions`, `session.url` | `POST /checkout/organizations/{orgId}/links` with `publish: true`, `shareUrl` |
| Payment Links | A link created in the dashboard, or without `publish` |
| `success_url`, `cancel_url` | `successUrl`, `cancelUrl` |
| `client_reference_id`, `metadata` | `clientReferenceId`, `metadata` (same alphabets and limits) |
| `checkout.session.completed` | `checkout_payment.paid` |
| `POST /v1/refunds`, `refund.reason` | `POST …/payments/{paymentId}/refund`, `reason` (`requested_by_customer`, `duplicate`, `fraudulent`, or our own `other`) |
| `charge.refunded` | `checkout_payment.refunded` or `checkout_payment.partially_refunded` |
| `charge.dispute.closed` (lost) | `checkout_payment.reversed` |
| `payment_link.active = false` | `POST /links/{linkId}/pause` (permanent) |
| `expires_at` | `expiresAt` on the link |
| `line_items`, `price_data` | `productId`, or `currency` + `items` |
| `payment_method_types` | `methods` (omitted means card) |
| Amounts in minor units | Decimal strings on links and events; base units with `decimals` on payment rows |

There are no subscriptions and no embeddable checkout.

## Testing

A test key pays links with a simulated buyer instead of test card numbers, and
the total picks the outcome. That includes chargebacks, which no card
processor's sandbox can produce ([Environments & sandbox](/environments/#checkout-outcomes-by-amount-suffix)).

## Integration guides

- [Set up checkout](/checkout-setup/)
- [Accept a checkout payment](/checkout/)
- [Refund a payment](/refunds/)
- [Receive and verify webhooks](/recipes/receive-and-verify-webhooks/)
