API reference
Checkouts
A checkout is a hosted payment page for one order. Create it on your server, send the buyer to its URL, and read it back whenever you need its current state. List them to reconcile your orders.
The checkout object#
| Field | Description |
|---|---|
idstring | Unique checkout id, for example chk_8Qe2TnV4kR. |
checkout_urlstring | The hosted checkout. Redirect the buyer here. |
statusstring | One of awaiting_payment, confirming, paid, underpaid, expired, review. See statuses. |
amountstring | The amount you asked for, with two decimals, for example "136.41". |
currencystring | Always "USD" in v1. |
expires_atstring | When the checkout stops accepting payment (ISO 8601, UTC). 60 minutes after creation. |
order_idstring | Your order reference, as you sent it. Returned by GET, the list and webhooks. |
created_atstring | When the checkout was created (ISO 8601, UTC). Returned by GET, the list and webhooks. |
paid_amountstring | How much has arrived so far, in USD. Returned by GET, the list and webhooks. |
payoutobject | Present once the checkout is paid: amount (string), asset ("USDC", "USDC.e" or "POL": the token the buyer paid with, never swapped) and tx_hash of the transfer to your wallet. |
payoutsarray | Only when a checkout was paid in more than one token (for example USDC and USDC.e): every transfer to your wallet, oldest first, each shaped like payout. payout is the first of them. Add the amounts up for the total. Omitted when there is a single transfer. |
Create a checkout#
/v1/checkouts"Your site" below means the site_url you sent when you got your keys, with or without www.
| Parameter | Description |
|---|---|
order_idstringrequired | Your order reference. Unique per merchant: sending the same order_id again returns the same checkout. |
amountstringrequired | Order total as a string with exactly two decimals, for example "136.41". |
currencystringrequired | "USD". |
customer_emailstringoptional | Pre-fills the buyer's email on the hosted checkout. |
return_urlstringrequired | Where the buyer goes after paying. HTTPS on your site. Not proof of payment. |
cancel_urlstringrequired | Where the buyer goes if they cancel. HTTPS on your site. |
callback_urlstringrequired | HTTPS endpoint on your site that receives webhooks for this checkout. |
BODY='{"order_id":"1042","amount":"136.41","currency":"USD","customer_email":"buyer@example.com","return_url":"https://shop.example/thanks","cancel_url":"https://shop.example/cart","callback_url":"https://shop.example/webhooks/caravel"}'
TS=$(date +%s)
SIG=$(printf '%s\n%s\n%s\n%s' "$TS" POST /v1/checkouts "$BODY" \
| openssl dgst -sha256 -hmac "$CARAVEL_SECRET" | awk '{print $NF}')
curl https://api.caravelpay.app/v1/checkouts \
-H "Content-Type: application/json" \
-H "Caravel-Merchant: $CARAVEL_MERCHANT" \
-H "Caravel-Timestamp: $TS" \
-H "Caravel-Signature: $SIG" \
--data-binary "$BODY"import { caravel } from "./caravel.mjs"; // signing helper from the Quickstart
const checkout = await caravel("POST", "/v1/checkouts", {
order_id: "1042",
amount: "136.41",
currency: "USD",
customer_email: "buyer@example.com",
return_url: "https://shop.example/thanks",
cancel_url: "https://shop.example/cart",
callback_url: "https://shop.example/webhooks/caravel",
});
res.redirect(303, checkout.checkout_url);<?php
require __DIR__ . '/caravel.php'; // signing helper from the Quickstart
$checkout = caravel('POST', '/v1/checkouts', [
'order_id' => '1042',
'amount' => number_format($order_total, 2, '.', ''),
'currency' => 'USD',
'customer_email' => 'buyer@example.com',
'return_url' => 'https://shop.example/thanks',
'cancel_url' => 'https://shop.example/cart',
'callback_url' => 'https://shop.example/webhooks/caravel',
]);
header('Location: ' . $checkout['checkout_url'], true, 303);from caravel import caravel # signing helper from the Quickstart
checkout = caravel("POST", "/v1/checkouts", {
"order_id": "1042",
"amount": f"{order_total:.2f}",
"currency": "USD",
"customer_email": "buyer@example.com",
"return_url": "https://shop.example/thanks",
"cancel_url": "https://shop.example/cart",
"callback_url": "https://shop.example/webhooks/caravel",
})
return redirect(checkout["checkout_url"], code=303){
"id": "chk_8Qe2TnV4kR",
"checkout_url": "https://pay.caravelpay.app/c/chk_8Qe2TnV4kR",
"status": "awaiting_payment",
"amount": "136.41",
"currency": "USD",
"expires_at": "2026-11-09T15:04:12Z"
}
Retries and order_id#
Network calls fail. Because order_id is unique per merchant, a retry with the same order_id returns the checkout you already created instead of making a second one. Retry timeouts and 5xx responses freely.
A checkout expires 60 minutes after it is created. If the buyer wants to try again after that, create a new checkout with a new order_id, for example 1042-2.
Retrieve a checkout#
/v1/checkouts/{id}Returns the checkout with its current status, paid_amount and, once paid, the payout. Sign it like any request, with an empty body.
TS=$(date +%s)
PATH_=/v1/checkouts/chk_8Qe2TnV4kR
SIG=$(printf '%s\n%s\n%s\n%s' "$TS" GET "$PATH_" "" \
| openssl dgst -sha256 -hmac "$CARAVEL_SECRET" | awk '{print $NF}')
curl "https://api.caravelpay.app$PATH_" \
-H "Caravel-Merchant: $CARAVEL_MERCHANT" \
-H "Caravel-Timestamp: $TS" \
-H "Caravel-Signature: $SIG"const checkout = await caravel("GET", `/v1/checkouts/${checkoutId}`);$checkout = caravel('GET', '/v1/checkouts/' . rawurlencode($checkoutId));checkout = caravel("GET", f"/v1/checkouts/{checkout_id}"){
"id": "chk_8Qe2TnV4kR",
"checkout_url": "https://pay.caravelpay.app/c/chk_8Qe2TnV4kR",
"status": "paid",
"amount": "136.41",
"currency": "USD",
"expires_at": "2026-11-09T15:04:12Z",
"order_id": "1042",
"created_at": "2026-11-09T14:04:12Z",
"paid_amount": "136.41",
"payout": {
"amount": "133.68",
"asset": "USDC",
"tx_hash": "0x5d1e8c0b7a4f2e19c6d3b8a0f5e7c2d1a9b4e6f8c0d2a7b5e3f1c9d8a6b4a07b"
}
}
List checkouts#
/v1/checkoutsYour checkouts, newest first, as full checkout objects. Use it to reconcile orders, or to find a payout's transaction hash.
| Query parameter | Description |
|---|---|
limitintegeroptional | Page size, from 1 to 100. Default 20. |
cursorstringoptional | The next_cursor from the previous page. |
Sign the path with its query string, exactly as you send it: /v1/checkouts?limit=50, then /v1/checkouts?limit=50&cursor=…. Pass the cursor through as you got it; don't build one yourself.
TS=$(date +%s)
PATH_='/v1/checkouts?limit=50'
SIG=$(printf '%s\n%s\n%s\n%s' "$TS" GET "$PATH_" "" \
| openssl dgst -sha256 -hmac "$CARAVEL_SECRET" | awk '{print $NF}')
curl "https://api.caravelpay.app$PATH_" \
-H "Caravel-Merchant: $CARAVEL_MERCHANT" \
-H "Caravel-Timestamp: $TS" \
-H "Caravel-Signature: $SIG"// Walk every page, newest first
let cursor = null;
do {
const query = new URLSearchParams({ limit: "100", ...(cursor ? { cursor } : {}) });
const page = await caravel("GET", `/v1/checkouts?${query}`);
for (const checkout of page.data) reconcile(checkout);
cursor = page.next_cursor;
} while (cursor);// Walk every page, newest first
$cursor = null;
do {
$query = http_build_query(array_filter(['limit' => 100, 'cursor' => $cursor]));
$page = caravel('GET', '/v1/checkouts?' . $query);
foreach ($page['data'] as $checkout) {
reconcile($checkout);
}
$cursor = $page['next_cursor'];
} while ($cursor !== null);# Walk every page, newest first
from urllib.parse import urlencode
cursor = None
while True:
query = urlencode({"limit": 100, **({"cursor": cursor} if cursor else {})})
page = caravel("GET", f"/v1/checkouts?{query}")
for checkout in page["data"]:
reconcile(checkout)
cursor = page["next_cursor"]
if cursor is None:
break{
"data": [
{
"id": "chk_8Qe2TnV4kR",
"checkout_url": "https://pay.caravelpay.app/c/chk_8Qe2TnV4kR",
"status": "paid",
"amount": "136.41",
"currency": "USD",
"expires_at": "2026-11-09T15:04:12Z",
"order_id": "1042",
"created_at": "2026-11-09T14:04:12Z",
"paid_amount": "136.41",
"payout": {
"amount": "133.68",
"asset": "USDC",
"tx_hash": "0x5d1e8c0b7a4f2e19c6d3b8a0f5e7c2d1a9b4e6f8c0d2a7b5e3f1c9d8a6b4a07b"
}
}
],
"next_cursor": null
}
next_cursor is null on the last page.
Return and cancel URLs#
After paying, the buyer is sent to return_url; if they back out, to cancel_url. Use these for the buyer's experience only. Payment can still be confirming when they land, and anyone can open those URLs, so fulfil orders from the webhook or a GET that shows paid.