What the buyer sees
Pick a token
The page offers the tokens in your store’s
enabledPaymentMethods and nothing
else. Choosing one starts a payment attempt.Pay a locked quote
Taberna locks a crypto quote, generates a fresh deposit wallet for this one
attempt, and shows the exact amount and address. The quote holds for up to 30
minutes — precisely, until
min(now + 30 minutes, the order or invoice's own expiry), because a locked quote must never outlive its parent.Watch it confirm
Taberna watches the chain and reports progress: detected, confirming, confirmed.
See confirmation progress.
Land back in your app
On completion the page shows a Continue button pointing at your
successUrl.
The dead-end states — expired, partial, and voided for invoices — link to
cancelUrl. Without them the buyer simply stays on the hosted page, so setting at
least successUrl is recommended.If the quote lapses
Nothing is lost. An attempt that expires holding zero funds leaves the parent order or invoice untouched and still payable — the buyer picks a token again and gets a fresh quote, and can keep doing that until the parent itself expires. An attempt that expires holding a non-zero but insufficient amount turns the parentpartial, which is terminal. See Payment lifecycle.
If the buyer switches token mid-attempt
Also fine, and worth understanding precisely:- Re-selecting the same token returns the same attempt — same address, same locked quote. Reloading the page is safe.
- Selecting a different token abandons the live attempt and locks a new quote on a newly generated address.
An abandoned attempt’s address stays watched until its own window ends. Funds
sent to it after the buyer switched still complete the order or invoice. Abandonment
is a UI affordance, not a lifecycle event — it emits no webhook.
The public checkout endpoints
The hosted page reads and drives payment through the/v1/checkouts endpoints. These
are deliberately unauthenticated.
Retrieve a checkout
GET /v1/checkouts/{id} — no auth.
{id} accepts either an order short id or an invoice short id; one resource serves
both hosted pages. The response is a tagged union: read type first, then the
branch fields.
- type: order
- type: invoice
items[].delivery is null until the order is completed — see
Products and delivery.Deliberately absent: customerEmail, extraContext, the payer IP. The page only
learns whether an email is on file, via hasCustomerEmail.404 with
{ "error": "Checkout not found" } — the same message for both resources, so the
endpoint never reveals which one you probed.
A malformed id never reaches the lookup. Every route that takes a short id
validates it against the short-id pattern first — 21 or 22 base58 characters — and an
id that fails that check is rejected by the validator as
400 { "error": "Invalid request data" }. 404 is reserved for ids that pass the
pattern and then either fail to decode or decode to nothing.The active attempt
activeAttempt is null when no quote is live, and otherwise:
cryptoAmount and receivedAmount are integer strings in the token’s smallest unit.
transactions is [] until a transfer is attributed.
Confirmation progress
confirmation is payer-facing progress, and nothing gates on it.
See Payment lifecycle for how each state arises and
what the per-chain depths are.
Start a payment attempt
POST /v1/checkouts/{id}/attempts — no auth. Driven by the checkout UI; you normally
do not call this.
Request
activeAttempt. Works on both branches.
Taberna refuses to price an attempt on a stale exchange rate rather than risk
mis-quoting it. That last error is transient — the buyer retrying a moment later
normally succeeds.
Attach a receipt email (orders only)
PUT /v1/checkouts/{id}/email — no auth.
Request
200 OK
This route is order-only. An invoice’s recipient is set by the merchant who
issued it, so passing an invoice id is a
400 — not a 404.Get a delivery download link (orders only)
GET /v1/checkouts/{id}/download/{orderItemId} — no auth.
Mints a short-lived presigned URL for a file_shared delivery on a completed order
item.
200 OK
expiresIn is the link’s lifetime in seconds — 300, five minutes.
Every substantive failure — unknown checkout, order not completed, wrong item, a
delivery that is not a file, an invoice id — answers with the same 404 and the
same message, { "error": "Download not available" }, so the endpoint never
reveals which check failed.
Both path segments are short ids and both are validated first, as everywhere else: if
{id} or {orderItemId} is not a well-formed short id the request is rejected as
400 { "error": "Invalid request data" } before any lookup runs.Polling
You may pollGET /v1/checkouts/{id} as a fallback, and the hosted page does exactly
that while an attempt is live. But for your integration:
Reasonable polling shape, if you need one:
status leaves pending (or open, for invoices): every
other state is terminal.