Caravel Pay Docs

Get started

Payment links

A payment link is a URL you can send anywhere: a chat, an email, a bio or a QR code. Each time someone opens it, they get a fresh hosted checkout and pay by card, Apple Pay, Google Pay or crypto. You don't need a website or API keys, just a Polygon wallet.

Two ways to make a link#

Without API keysWith API keys
HowSend payout_address with no signature, or use the link pageSign the request like any other call
Paid toThe wallet in payout_addressYour merchant's payout wallet
WebhooksNone. Payouts arrive in your wallet automaticallyYes, to your callback_url
See the checkoutsIn your wallet's historyGET /v1/checkouts and GET /v1/payment-links/{id}
Turn a link offNot possible: make a single-use link if you need it to closePOST /v1/payment-links/{id}/deactivate

A link made without keys returns no credentials, so nobody, including you, can read its checkouts through the API. That is what makes it safe to create one from a public page.

Create a payment link#

POST/v1/payment-links
ParameterDescription
payout_addressstringwithout keysYour Polygon wallet, 0x and 40 hex characters. Use a plain wallet you control (MetaMask, Rabby, a hardware wallet), not an exchange deposit address or a smart-contract wallet. Only for links made without keys; a signed request that sends it gets 400.
amountstring or nullrequiredThe price with exactly two decimals, like "25.00", from 1.00 up to your per-checkout limit (1000.00 for new merchants). Send null to let the buyer enter the amount.
min_amount, max_amountstringOnly with amount: null. The range the buyer may enter. Default 1.00 to your per-checkout limit.
currencystringrequiredAlways "USD".
descriptionstringOptional plain text, at most 80 characters, shown to the buyer as Note from the seller. No links, web addresses or HTML.
reusablebooleanDefault true: anyone may pay through the link, any number of times. false: the link closes after one successful payment.
callback_urlstringSigned links only. HTTPS on one of your merchant origins. Webhooks for this link's checkouts go here. Leave it out for no webhooks.
return_urlstringSigned links only. Where the buyer goes after paying, on your merchant origins. Default: a neutral Caravel Pay "Payment received" page.

Without API keys#

No headers beyond Content-Type. The answer is the link's id and URL, nothing else.

POST /v1/payment-links (no keys)
201 Created
{
  "id": "lnk_K7Q2ZX4PRBM5TW3NHJ6D",
  "url": "https://pay.caravelpay.app/l/lnk_K7Q2ZX4PRBM5TW3NHJ6D"
}

Links for the same wallet share one behind-the-scenes merchant, with the same 2% Caravel fee and limits as any new merchant. The wallet is checked the same way as at connect: sanctioned addresses, contracts and Caravel Pay's own addresses are refused with 400 invalid_request, param: "payout_address". Each IP address may create 30 links an hour.

With API keys#

Signed like any request. The link belongs to your merchant: its checkouts show up in GET /v1/checkouts, and webhooks go to its callback_url.

POST /v1/payment-links (signed)
201 Created
{
  "id": "lnk_M5TW3NHJ6DK7Q2ZX4PRB",
  "url": "https://pay.caravelpay.app/l/lnk_M5TW3NHJ6DK7Q2ZX4PRB",
  "status": "active",
  "amount": "49.00",
  "min_amount": null,
  "max_amount": null,
  "currency": "USD",
  "description": "Workshop seat",
  "reusable": false,
  "return_url": null,
  "callback_url": "https://shop.example/webhooks/caravel",
  "payments_count": 0,
  "checkouts_count": 0,
  "created_at": "2026-11-09T21:04:12Z",
  "deactivated_at": null
}

Let the buyer choose the amount#

Send "amount": null for tips, donations or invoices of varying size. The buyer first sees a short "Enter the amount to pay" step, then the normal checkout. Set min_amount and max_amount to narrow the range.

Request body
{
  "payout_address": "0x52908400098527886E0F7030069857D2E4169EE7",
  "amount": null,
  "min_amount": "5.00",
  "max_amount": "250.00",
  "currency": "USD",
  "description": "Tips for the crew"
}

The payment link object#

Returned by the signed calls. Links made without keys return only id and url.

FieldDescription
idstringlnk_ and 20 characters.
urlstringThe link to share.
statusstringactive, completed (a single-use link that has been paid) or deactivated.
amountstring or nullThe fixed price, or null when the buyer enters it.
min_amount, max_amountstring or nullThe buyer's range for an open amount; null for a fixed price.
currencystring"USD".
descriptionstring or nullThe note shown to the buyer.
reusablebooleanWhether more than one payment may be made.
return_url, callback_urlstring or nullAs sent.
payments_countintegerCheckouts from this link that are paid.
checkouts_countintegerCheckouts opened from this link, paid or not.
created_at, deactivated_atstringISO 8601 UTC. deactivated_at is null until you deactivate the link.

Retrieve a payment link#

GET/v1/payment-links/{id}

Signed, with an empty body. A link that isn't yours answers 404 not_found, exactly like one that doesn't exist.

GET /v1/payment-links/{id}

Deactivate a payment link#

POST/v1/payment-links/{id}/deactivate

Signed, with an empty body or {}. The link stops opening new checkouts at once and buyers see "This payment link is no longer active". Checkouts that were already open can still be paid, and those payouts reach you as usual. Calling it again changes nothing. There is no way to turn a link back on: create a new one.

POST /v1/payment-links/{id}/deactivate
200 OK (excerpt)
{
  "id": "lnk_M5TW3NHJ6DK7Q2ZX4PRB",
  "status": "deactivated",
  "deactivated_at": "2026-11-10T09:30:00Z"
}

What the buyer sees#

  1. They open the link. A fixed price continues straight to the checkout; an open amount asks for the amount first.
  2. The hosted checkout shows the amount and your note, never a store name or logo, and lets them pay by card, Apple Pay, Google Pay or crypto. Any card provider fee is shown before they pay.
  3. After paying they land on a neutral "Payment received" page (or your return_url for a signed link).

Every opening creates a new checkout with order id lnk_…-1, lnk_…-2 and so on. A buyer who opens the link again in the same browser session gets their unpaid checkout back instead of a new one. Each checkout expires 60 minutes after it opens, like any other.

Webhooks and reconciliation#

For a signed link with a callback_url, each checkout sends the usual webhooks; the checkout's order_id starts with the link id. Links made without keys send no webhooks: watch your wallet, where each payout arrives automatically.

Single-use links: the link closes after its first paid checkout, and while a payment is confirming nobody else can start one. A buyer who opened the link earlier can still finish their checkout, and that payment reaches you as usual.

Fees#

The same as any payment: you receive the amount minus the flat 2% Caravel Pay fee, in your wallet, automatically once the payment confirms. The buyer pays their card provider's fee, shown to them before they pay.

Errors#

CodeWhen
invalid_amount 400amount, min_amount or max_amount isn't a two-decimal string in range, or min_amount is above max_amount.
invalid_request 400A missing or unknown field, a bad payout_address or wallet, or a description with a link, HTML or more than 80 characters. param names the field.
invalid_url 400callback_url or return_url isn't HTTPS on your merchant origins.
unsupported_currency 400currency isn't "USD".
merchant_paused 403Payments to this wallet or merchant are paused.
not_found 404No link with that id belongs to your merchant.
limit_exceeded 409The wallet already receives payouts for the most merchants allowed.
rate_limited 429Too many links from your address. Wait for Retry-After.

All codes and the retry rules are on Errors.