Tesser models funds movement using a single shared data shape and lifecycle. The resource types differ mainly in their direction and which pre-execution checks apply. Their data model, step structure, status taxonomy, and webhook conventions are the same.
Resource Types
Tesser exposes four funds-movement resources (payments, deposits, withdrawals, and rebalances). Each is modeled with the same shared envelope (described in Resource Data Model) and progresses through the same Lifecycle Phases.
- Payment — funds movement to or from an external counterparty. Payouts (
direction= outbound) are created via POST /v1/payments. Inbound payments (direction= inbound) cannot currently be pre-registered but will be recorded and surfaced via webhooks and the API. - Deposit — inbound funds movement that on-ramps fiat from your bank to a Tesser-managed account, with optional conversion into stablecoin at a liquidity provider. Created via POST /v1/treasury/deposits.
- Withdrawal — outbound funds movement that off-ramps stablecoin held at Tesser-managed accounts back to your bank. Created via POST /v1/treasury/withdrawals.
- Rebalance — internal funds movement between two managed accounts (provider ledger to wallet, wallet to wallet, ledger to ledger). Created via POST /v1/treasury/rebalances.
Resource Data Model
Each resource type is represented as a single JSON object with a consistent set of top-level fields, a desired / estimated / actual overlay, and the steps[] array. Resource-specific fields (risk_status, balance_status, funding_account_id) are added as applicable at the top-level of the record — see the Resource-Specific fields for which applies where.
Shared Top-Level Fields
All four resource types include the following top-level fields:
id— Tesser-assigned UUID for the resource.workspace_id— UUID of the workspace that owns the resource.organization_reference_id— optional client-supplied identifier (e.g., your internal payment or treasury operation ID).direction— one ofoutbound,inbound, orrebalance. See the matrix below for which value each resource takes.created_at— timestamp the resource was created at Tesser.updated_at— timestamp of the most recent change to the resource.expires_at— timestamp after which the resource will no longer execute. See Expiration.steps[]— ordered array of execution steps Tesser plans for the resource. Populated shortly after creation and supplied via the<resource>.quote_createdwebhook.desired/estimated/actual— the overlay describing intent, projection, and outcome. Each containsfromandtofields.
Resource-Specific Fields
Some fields and concepts apply only to a subset of resource types. The table below summarizes which top-level concepts appear on which resources.
| Field / Concept | Payment | Deposit | Withdrawal | Rebalance |
|---|---|---|---|---|
risk_status (risk check) | yes | no | no | no |
balance_status (balance check) | yes | no | yes | yes |
funding_account_id | yes | no | no | no |
direction value | outbound, inbound | inbound | outbound | rebalance |
- Risk check screens the external sending or receiving wallet of a payment.
- Balance check reserves funds in the
desired.from.account_id; applied whenever funds are debited from a managed account (outbound payments, withdrawals, rebalances). funding_account_idis unique to outbound payments — it identifies which source bank account ultimately funds the payout. See Travel Rule.
The Desired / Estimated / Actual Overlay
Funds movement at scale involves uncertainty: liquidity providers may not guarantee exchange rates, on-chain transactions may fail, and resources can terminate at intermediate steps if they don't complete by expires_at. To handle this gracefully, every resource carries three parallel snapshots of its from and to fields.
- Desired — your stated intent at creation: the source and destination accounts, currencies, networks, and amount. Set on the create request and never overwritten afterward. This preserves intent for downstream reconciliation regardless of how the resource ultimately concludes.
- Estimated — Tesser's best projection for how the resource will proceed if it succeeds. Populated after planning and quoting (see Planning). For resources that involve a swap or cross-currency step, the ratio of
estimated.from.amounttoestimated.to.amountis the indicative exchange rate. For same-currency moves,estimated.*matchesdesired.*(1:1). - Actual — what actually settled. Populated as steps complete and at the resource level when the resource reaches a terminal state. On success,
actual.*matchesestimated.*. On failure,actual.*reflects the final state — possibly different account, currency, or network thandesired.*.
Each overlay contains a from and to field with the same four sub-fields:
account_id— the Tesser account UUID for that step.amount— string-encoded decimal amount.currency— currency code (e.g.,USD,USDC,MXN).network— blockchain network code (e.g.,BASE,POLYGON), ornullfor non-network steps (bank accounts, provider ledgers).
Overlay applicability
The desired fields are only included on the top-level of the record. The estimated and actual overlay structure is repeated on each step.
| Overlay | Top-Level | Step |
|---|---|---|
| Desired | Yes | No |
| Estimated | Yes | Yes |
| Actual | Yes | Yes |
The estimated.from.* fields at the top level of the record are populated from the desired.from.* fields. The estimated.to.* fields represent Tesser's best estimate for the outcome of last step of the resource.
Steps
The steps[] array represents the ordered sequence of steps Tesser plans to execute. Each step has the estimated and actual overlay, plus a status and timestamps for each phase transition.
Two step types are currently used across all resource types:
transfer— funds move from one account to another. The source and destination accounts may differ in currency or network. Examples: an on-chain stablecoin transfer between two wallets; a fiat push from a bank to a liquidity provider's bank; a last-mile fiat payout via a local payment network.swap— currencies are exchanged within the same account (the step'sestimated.from.account_idandestimated.to.account_idare equal). Example: at a liquidity provider, selling USD and buying USDC inside the same provider ledger.
Each step has a status. See Step Statuses for more information.
Fees
Fees are reported as an array in the per-step fees[] object. Fees include blockchain network (gas) fees or provider fees. Each entry includes:
fee_amountfee_currencyfee_type- (Optional)
fee_metadata
Depending on the type, the amount of a fee may not be known until the step has executed (e.g. gas fees). Other times, the fee may be known in advance (e.g. if a fiat off-ramp charges a separate transaction fee for a payout).
Lifecycle Phases
Every resource progresses through four phases: planning, pre-execution checks, execution, and terminal state. Each phase emits webhooks; the prefix is the resource type (payment.*, deposit.*, withdrawal.*, rebalance.*).
Planning
When you POST the create request, Tesser creates the resource synchronously and begins to plan the sequence of steps to execute the request, including the quote.
The quote includes:
- Exchange rate — The ratio of
estimated.from.amounttoestimated.to.amountis the indicative exchange rate betweenestimated.from.currencyandestimated.to.currency.- For stablecoin-to-stablecoin same-currency transfers, the rate is always 1:1.
- For cross-currency transfers, Tesser sources the best exchange rate from available liquidity providers.
- From or To amount — For payments, you may request a quote based on the amount to send (specify
desired.from.amount) or the to amount to receive (specifydesired.to.amount). Tesser determines the other side of the overlay based on the prevailing rate. For deposits, withdrawals, and rebalances, you specify thedesired.from.amount.
Tesser then fires the <resource>.quote_created webhook. This event always fires, even for same-token, same-network moves, to keep the lifecycle uniform across resource types.
The <resource>.quote_created webhook contains the planned steps[] array. Each step has a status of created. At this point the desired overlay is populated on the top-level of the resource. The estimated overlay is populated at the top level and on each step. actual.* fields are all null.
Pre-Execution Checks
Before steps begin executing, Tesser runs the checks that apply to the resource (per the Resource-Specific fields above).
- Risk check (payments only) — screens the destination wallet for sanctions and other risk signals. The
payment.risk_updatedwebhook reports the outcome viarisk_status(see Risk Statuses). If your organization's policy requires manual review for the result, a user with sufficient permissions can review the payment in the Tesser dashboard or submit a decision viaPOST /v1/payments/{paymentId}/risk-review.
For inbound payments, you do not have to report your decision to the risk review API, but it is recommended so that you can keep your transaction history in Tesser's platform reconcilable and auditable.
- Balance check (payments, withdrawals, rebalances) — reserves source funds on the resource's source account. Two outcomes are possible:
reserved(sufficient funds; the resource proceeds to execution) orawaiting_funds(insufficient funds; the resource is queued and will retry untilexpires_at). Reported via<resource>.balance_updated.
Where the source account is a provider ledger (e.g. Circle ledger, OpenFX ledger), Tesser performs the balance check automatically by reading the ledger's available balance.
Where the source account is a self-custodial wallet, the balance check is part of the step-signing process: Tesser emits step.signature_requested, you sign the step locally and submit the signature, and the synchronous response plus the subsequent <resource>.balance_updated webhook reflect the reservation outcome.
Execution
Once pre-execution checks pass, Tesser executes each step in order. Steps within the same resource may overlap (a fiat off-ramp step may begin before a preceding on-chain step has fully finalized), but each individual step transitions through the same status states in sequence.
The step status progression when on-chain signing is not applicable is:
Code
The step status path when on-chain signing is required is:
Code
Each transition emits a step.<status> webhook. See Step Statuses for definitions.
Sometimes, a step's status will progress directly from created to completed or failed. This can happen when providers emit limited webhooks to Tesser, which reduces the visibility Tesser has to granular intermediate movements.
Terminal State and Divergence
When the last step reaches a terminal state, Tesser populates the top-level actual.* overlay and emits a <resource>.updated webhook carrying the full updated resource object. You can use this webhook to observe the terminal state without GET-ing the resource.
completed—actual.from.*andactual.to.*are populated. Where the resource includes a swap step, the actual exchange rate may differ slightly from the indicative quote.failed—actual.*reflects what actually occurred. The resource may have terminated mid-route, leaving funds at an intermediate account in an intermediate currency. For example, an on-ramp deposit that fails at its swap step terminates withactual.to.currency = "USD"(fiat at the provider's ledger) even thoughdesired.to.currency = "USDT".
Statuses Reference
This section catalogs the status taxonomies used across the lifecycle.
Risk Statuses
Result of risk review performed on the destination wallet. Risk statuses appear on payments only; see the Resource-Specific fields.
| Status | Terminal | Webhook event type | Description |
|---|---|---|---|
unchecked | No | Not sent. All payments at creation have a risk status of unchecked. | Beneficiary wallet identifier has not been supplied or risk check has not yet completed. |
awaiting_decision | No | payment.risk_updated | Beneficiary wallet has been risk screened and requires manual review to determine whether to send the payout. |
auto_approved | Yes | payment.risk_updated | Beneficiary wallet has been risk screened and automatically approved per your organization's policy. |
manually_approved | Yes | payment.risk_updated | Beneficiary wallet has been risk screened and has been manually reviewed and approved. |
auto_rejected | Yes | payment.risk_updated | Beneficiary wallet has been risk screened and automatically rejected per your organization's policy. |
manually_rejected | Yes | payment.risk_updated | Beneficiary wallet has been risk screened and has been manually reviewed and rejected. |
Balance Statuses
Result of the balance check on the source account. Balance statuses appear on payments, withdrawals, and rebalances; see the Resource-Specific fields. The webhook event type uses the resource-type prefix — e.g. payment.balance_updated, withdrawal.balance_updated, rebalance.balance_updated.
| Status | Terminal | Webhook event type | Description |
|---|---|---|---|
unreserved | No | Not sent. All resources at creation have a balance status of unreserved. | Source account has not been supplied or the reserve operation has not yet completed. |
awaiting_funds | No | <resource>.balance_updated | The balance of the source account was checked and there are insufficient funds. The resource is queued and awaits funding (e.g., from a deposit) until expires_at. |
reserved | Yes | <resource>.balance_updated | The balance of the source account was checked and funds were reserved to process the resource. |
Step Statuses
Result of step execution. Step statuses and their webhooks (step.*) are shared across all four resource types and all step types (transfer, swap, future types).
| Status | Terminal | Webhook event type | Description |
|---|---|---|---|
created | No | Not sent. All steps at creation have a status of created. | Tesser has created a record for this step. |
signature_requested | No | step.signature_requested | Signature has been requested for the step |
signed | No | step.signed | Step has been cryptographically signed |
submitted | No | step.submitted | Tesser submitted the step information to the blockchain or fiat payment network. |
confirmed | No | step.confirmed | The step was accepted by the operator of the payment network. |
completed | Yes | step.completed | The step delivered funds to the destination account. For fiat steps, indicates the local payment network has delivered funds. Some fiat networks may not provide formal confirmation, in which case funds are assumed delivered unless failed is indicated. |
failed | Yes | step.failed | The step was not successful; funds were not transferred from the source to the destination. |
Some step types collapse to a terminal-only step.completed (or step.failed) — for these, Tesser doesn't fire intermediate step.submitted or step.confirmed events. See Execution.
Expiration
Every resource has an expires_at timestamp. The validity period varies by resource type, currency pair, route, and amount. After expiration, the resource will not execute and a new resource must be created to retry.
Why resources expire
Even same-currency, same-network resources have an expires_at. Risk and compliance checks must be re-run after a certain amount of time has passed if the resource has not been executed, and cross-currency transfers need their quotes refreshed.
If a resource expires before completing, for example, a payment whose balance_status never reaches reserved because balance in the desired.from.account_id was not replenished in time, or a deposit whose terminal step never confirmed — its actual.* overlay reflects the last observed state. To retry, create a new resource with fresh desired.* fields; you cannot resurrect an expired resource.