Skip to main content
Three state machines are in play, and keeping them apart is most of what there is to understand about Taberna.
  • The order or invoice is the thing you created. It is payable, then terminal.
  • A payment attempt is one quote-locked session under it. Several may come and go during a single order’s life.
  • A confirmation is a presentation-layer read of how deeply a transfer is buried. Nothing gates on it.

Orders

Invoices

Same machine as orders, with paid in place of completed and one extra terminal state, voided, for a merchant cancelling before payment. Voiding is refused while an attempt is live — see the void guard.

Payment attempts

An attempt is created when the buyer picks a token. It locks a crypto quote, mints a fresh deposit wallet used for that attempt alone, and is watched until it resolves. Three consequences worth internalising:
Only one attempt can be processing at a time. Re-selecting the token that is already live returns the same attempt — same address, same quote — so reloading the checkout page is safe and does not mint a second address.
Switching tokens marks the old attempt abandoned, but it stays watched until its own window ends, and it can still complete. Funds a buyer broadcast just before switching are not lost.Abandonment emits no webhook. It is a UI affordance, not a lifecycle event.
An attempt that expires holding zero leaves the parent pending / open. The buyer picks a token again, gets a fresh quote on a fresh address, and can keep doing that until the parent itself expires.

Partial payments

There is no underpayment tolerance. Completion is a strict comparison: the balance must be greater than or equal to the quoted cryptoAmount. A payment one base unit short does not complete.
While an attempt is live, an insufficient balance simply sits there — Taberna keeps re-reading the deposit address, so the buyer can top it up and the attempt completes the moment the total clears the quote. Nothing is decided until the window closes. At the attempt’s expiresAt, exactly one rule applies: Overpayment completes normally. The comparison is >=, so a buyer who sends more than quoted gets a completed order; the surplus is part of the swept amount.

Surfacing a partial

partial is terminal on the parent and is not resolved automatically — there is no automatic refund. What you get:
  • An order.partial or invoice.partial webhook.
  • receivedAmount and cryptoAmount on the order, or under payment on the invoice, so the shortfall is cryptoAmount − receivedAmount in the token’s smallest unit.
  • transactions[], so you can trace what actually arrived and from where.
Deciding what to do — refund off-platform, honour the purchase anyway, ask the buyer to send the difference through a new charge — is a merchant policy call. Do not fulfil on partial unless you have decided to honour underpayments.

Confirmations

activeAttempt.confirmation on the public checkout payload reports how deeply the inbound transfers are buried. confirmations is clamped to at most required, so it never overshoots. Both are null when unknown.
stale: true means the counts cannot be trusted — the chain-head reading behind them is missing or older than 60 seconds. Do not render progress while it is set.And confirmed is not “paid”. It says the transfers on file are buried deeply enough; it says nothing about whether they add up. An underpayment can sit at confirmed indefinitely. The authoritative signal is always the order or invoice status.

Per-chain confirmation depths

These values reach the API through the chain indexers rather than being hardcoded in it, which is why required can legitimately be null: no depth has been published for that chain yet. Treat a null as “unknown”, never as “zero”.

Expiry windows

The attempt window is precisely min(now + 30 minutes, the parent's expiresAt). A locked quote must never outlive its parent, so an attempt started five minutes before an order expires gets five minutes, not thirty.
A wider order window holds reserved delivery stock out of circulation for exactly as long. If you widen expiresIn for finite-stock products, expire abandoned orders through the expire endpoint rather than waiting them out.
Expiry is swept by a background pass rather than evaluated on read, so a resource can sit a few seconds past its expiresAt before its status changes. Poll for the status change; do not compute expiry client-side.

Supported chains and currencies

Fiat pricing currencies: usd and eur. Thirteen payment methods in total. Which of them a buyer can actually pick is your store’s enabledPaymentMethods, readable at any time from GET /v1/store.
Stablecoins on a low-fee chain — usdc_base, usdc_polygon — are the smoothest default: the buyer’s gas cost is negligible and there is no exchange-rate movement inside the quote window to explain away.

Settlement

Each payment lands in the deposit wallet Taberna generated for that attempt, and is then swept on-chain to your store’s payout wallet for that chain — the address you registered in the dashboard. There is no Taberna balance to withdraw from and no payout schedule.

The processing fee

The sweep splits the swept balance: the processing fee goes to Taberna, the remainder to your payout wallet. The standard rate is 3%; it is set per store, so check the dashboard for the rate that applies to yours. The deduction happens on-chain, in the sweep transaction itself, so what arrives at your address is already net of it. That is the only deduction Taberna makes: Because sweep gas is covered by Taberna, the processing fee is the whole cost of settlement. Processors that charge a percentage and then bill network or withdrawal fees separately deduct on more than one line.

What gets swept, and when

A funded deposit wallet is swept once its attempt has settled — the sweepers pick up attempts in completed, expired or partial. expired and partial are included so that funds which arrived too late, or fell short of the quote, still reach your payout wallet rather than being stranded. An abandoned attempt is not swept while it is abandoned. It stays watched to the end of its own window and then settles into completed, partial or expired like any other attempt; the sweep follows from that settled status, not from the abandonment.
Sweep state is internal plumbing. It appears nowhere on the public API — not on the buyer-facing checkout payload and not on GET /v1/orders/{id} or GET /v1/invoices/{id}. Settlement progress is visible only in the dashboard. Your completion signal is the order or invoice status, never the sweep.