Receive an Inbound Payment to a Wallet
An inbound payment is a Tesser Payment resource with direction = "inbound". It represents funds sent on-chain from an external wallet to one of your managed accounts at Tesser. This guide covers the wallet-recipient flow: inbound payments credited to a managed wallet at Tesser. Before reading further, review the Funds Movement Lifecycle and Data Model for the shared data shape, lifecycle phases, and status taxonomy that apply to inbound payments along with Tesser's other funds-movement resources, and Compliance and Risk Management for the two compliance checks — Travel Rule data exchange and wallet risk screening — that shape the inbound lifecycle.
What's Different About Inbound Payments
Inbound payments share the Payment resource shape with outbound payments but diverge in several ways. Note these before you begin your integration:
- Two entry paths. An inbound payment either begins with a Travel Rule authorization request from the originator VASP, in which case the payment record is created before funds move, or arrives as a direct on-chain transfer, in which case the record is created when Tesser sees the transaction in a block. See How an Inbound Payment Begins.
- No customer-side planning. The
desiredoverlay does not populate.estimatedpopulates when Tesser records the inbound payment;actualpopulates only when the block finalizes (atstep.completed/payment.updated). There is no quote, nopayment.quote_createdwebhook, and noPATCHstep. - No balance check.
balance_statusisnullfor the life of an inbound payment. Funds are being credited to the wallet's balance, not debited from it, so there is no balance to reserve and nounreserved→reservedtransition to report. - No signing. Your integration never calls a sign endpoint for an inbound payment. The on-chain transaction was signed by the external sender, not by you.
- Exactly one step. The payment contains exactly one step, of type
transfer. Inbound payments get created with stepstatusascreatedwhen the record predates the transaction (a Travel Rule authorization flow) and with stepstatusasconfirmedwhen the record is created from a direct on-chain transfer without the Travel Rule flow. Stepstatuswill beconfirmedonce the transaction is in a block,completedonce the block finalizes, andfailedin the case where the funds do not settle — a rejected authorization, a sender who never broadcasts, or a block that never finalizes. - No expiration.
expires_atis alwaysnullon an inbound payment. You are receiving funds on-chain rather than instructing a payment, so there is nothing to expire. - The originator is identified through
participants[], not supplied by you. Tesser reconciles the originating participant, and the VASP hosting the sending wallet, if there is one, against your counterparty and tenant records, and records the outcome in the payment'sparticipants[]array. See How the Sending Wallet Is Identified. funding_account_idisnull. Inbound records identify the sender throughestimated.from.account_idandparticipants[], never throughfunding_account_id.- Risk screening targets the sender and is advisory. Because the recipient is your managed wallet, Tesser screens the external sending wallet against your organization's risk policy and notifies you of the outcome via
payment.risk_updated. Screening runs in parallel with the on-chain lifecycle and does not gate the credit — the receiving wallet's balance updates when the transaction is confirmed in a block, regardless of the risk outcome.
How an Inbound Payment Begins
An inbound payment can begin one of two ways:
- Through a Travel Rule authorization flow. The sending (originator) VASP requests authorization through a Travel Rule data-exchange provider before broadcasting the transaction on-chain, and exchanges identity information as part of that request. Tesser creates a payment record and populates it with the data available from the Travel Rule provider.
- As a direct on-chain transfer. A sending wallet pushes funds to the wallet with no prior authorization request. No Travel Rule data accompanies the transfer, so Tesser creates the payment record when it sees the transaction in a block and infers what it can about the sender from screening the sending wallet through a blockchain analytics provider.
A note on settlement
For inbound payments, funds are credited to your available balance regardless of the screening outcome. Tesser's risk_status is an advisory signal you consume to decide whether to credit your end user; it is not a hold on funds Tesser has already credited. See Why Availability Isn't Gated on the Screening Outcome.
Continuous Monitoring of Wallets for Inbound Payments
You do not need to pre-register inbound payments in order to receive funds on-chain. Tesser monitors wallets for on-chain activity and when an inbound transaction is detected, will create a payment record, counterparties and accounts (if needed), and notify your integration via webhooks. Tesser will first become aware of an on-chain transaction when that transaction is confirmed in a block.
When funds arrive on-chain, Tesser looks up the transaction against the Travel Rule data-exchange provider. If it corresponds to an authorization or Travel Rule message, the transfer is reconciled to the payment record created from that flow rather than creating a second record.
How the Sending Wallet Is Identified
Tesser identifies the participant(s) on the other side of an inbound payment in the payment's top-level participants[] array. For an inbound payment, the array can identify the ORIGINATOR, the ORIGINATOR_VASP, a CUSTODY_SERVICE and/or INTERMEDIARY_VASP, the BENEFICIARY (the counterparty or tenant that owns the receiving wallet), or a CONTRACT when the sending address is a contract rather than an end-user wallet.
Which roles populate, where each one's identity data comes from, and how each is reconciled to your records depends on whether Travel Rule data accompanied the transfer and whether the sending wallet is VASP-hosted or self-hosted.
See Payment Participants for the per-role tables, the resource object's fields, and the full set of resolution values.
Four points matter most for an inbound integration:
- Names are matched exactly. Provider-supplied names are scored against your counterparty and tenant records; a record is matched only on an exact match. Where a non-beneficiary participant does not match, Tesser creates a new counterparty for it and the entry carries
resolution: "COUNTERPARTY_CREATED". - Your beneficiary record is canonical. Tesser never creates a new beneficiary record from provider data. If an originator VASP supplies a beneficiary name that does not match your record, the
BENEFICIARYentry carriesresolution: "DID_NOT_MATCH". An inbound payment withresolution: "DID_NOT_MATCH"for theBENEFICIARYrole is how you know a Travel Rule authorization was received but rejected prior to funds movement. Where no provider-supplied beneficiary name existed to match at all, the entry carriesresolution: "USER_SUPPLIED_ID". - One sending wallet can produce more than one array entry. A hot wallet used to send funds out of an exchange, for example, may be attributed both to the exchange itself (
ORIGINATOR_VASP) and to the third-party custodian that holds its wallets (CUSTODY_SERVICE). - Travel Rule data takes precedence for entity attribution. Where a Travel Rule authorization flow supplied the identity data, Tesser treats it as the source of truth for the roles it covers. Wallet screening attribution still fills in roles the Travel Rule payload does not describe, e.g. a custody service or an intermediary VASP the originator VASP did not declare.
Recording the Sending Wallet on the Payment
When Tesser records an inbound payment from a wallet address it has not seen before, it automatically creates an unmanaged wallet account (is_managed: false) for the sending wallet and assigns it to the counterparty the sender reconciled to. If Tesser recognizes the wallet address — because the sender is a repeat sender, or because an outbound payment previously sent funds to this address — Tesser identifies the existing account by looking up the wallet address.
estimated.from.account_id populates as soon as Tesser has the transaction, because the sending address is on the transaction itself — it does not wait for the screening outcome. For a direct on-chain transfer it is present from record creation; for a Travel Rule flow it fills in when funds land on-chain. The participants[] entry that identifies who that wallet belongs to arrives later, with the screening result. See When Each Field Populates.
The auto-created account follows this shape:
Code
The account is assigned to whichever record the sender reconciled to. Read the corresponding participants[] entry's resource.resource_type to know which: for a COUNTERPARTY the account carries counterparty_id, and looking that counterparty up returns its tenant_id for tenanted integrations; for a TENANT the sender resolved to one of your tenants directly; WORKSPACE means the funds came from one of your own treasury wallets. Subsequent inbound payments from the same wallet address reuse this account.
The sending account's assets[].available_balance stays "0". Tesser does not monitor balances on unmanaged accounts — the record exists to identify where the funds came from, not to track what that wallet holds.
Counterparty Names for Unattributed Sending Wallets
Where the sender cannot be identified by name (the transfer carried no Travel Rule data, and the wallet screening provider attributes the address to an unnamed wallet cluster rather than a named entity), Tesser still creates a counterparty so the payment has something to reconcile to. These counterparties are named after the cluster: the words "Wallet Cluster" followed by the full wallet address, for example Wallet Cluster 0x742d35Cc6634C0532925a3b844Bc9e7595f8fE0B.
A cluster groups the address(es) the screening provider believes are controlled by the same owner. A counterparty with a Wallet Cluster name therefore means "we know these addresses belong to one owner, but not the specific identity of that owner." Repeat sends from any address in the cluster reconcile to the same counterparty.
When Each Field Populates
An inbound payment record is created at the earliest point Tesser has something to record, and its remaining fields fill in as the compliance checks resolve.
| Stage | What has happened | What populates |
|---|---|---|
| Travel Rule authorization request received (Travel Rule flows only) | Tesser runs the beneficiary-name match and authorizes or rejects the request, then creates the payment record and its single transfer step in created status. Funds have not yet moved. | estimated.to.account_id, and the estimated amount, currency, and network at both the top level and the step level; participants[]; the TRAVEL_RULE entry in provider_metadata[]. estimated.from.account_id is left blank. risk_status is unchecked and risk_status_reasons[] is empty. |
| Funds land on-chain | For a Travel Rule record, Tesser reconciles the transfer to it and now knows which wallet sent the funds; the step moves from created to confirmed. When no record matches, Tesser creates one now, with its step already in confirmed. Either way, wallet screening kicks off and the receiving wallet is credited. | The step's status, transaction_hash, and confirmed_at; and estimated.from.account_id. The receiving wallet's available and total balance are credited. For records created at this stage: estimated.to.account_id, network, and estimated amounts are also populated, with participants[] empty, risk_status unchecked, and risk_status_reasons[] empty. |
| Screening resolves | The wallet screen completes and Tesser determines whether the sender's identity reconciles to existing records or requires new counterparties. | risk_status and risk_status_reasons[]; the WALLET_RISK_SCREENING entry in provider_metadata[]; and, for records created at the funds-landing stage, participants[], all at once. |
| Block finalizes | The transaction is final and can no longer be reorganized away. | The step moves to completed; the step-level and top-level actual overlays populate. The balance does not change here — it was credited at confirmation, and finalization makes that credit permanent. |
Manual review (only when risk_status is awaiting_decision) | Your reviewer records a decision. | risk_status moves to manually_approved or manually_rejected; risk_reviewed_by and risk_reviewed_at populate. risk_status_reasons[] is unchanged from the automatic outcome. |
participants[] is populated once in a single write. For the timing across all payment directions, see When participants[] Populates.
Inbound Payment Creation (payment.created)
payment.created fires when Tesser creates the payment record. When that happens depends on the entry path.
About the payload examples on this page
The webhook payloads below are abridged to the fields that matter at each stage — // ... marks fields present on the wire but omitted here. Every payload carries the complete payment resource, wrapped in the standard { id, type, created_at, data: { object } } envelope. For the full field list see the Payments API reference; for the envelope, see Webhooks.
Travel Rule Authorization Flow
For a payment that begins with a Travel Rule authorization request, payment.created fires before funds move. The payload records the destination account, the reconciled participants, the raw transfer record in provider_metadata[], and the single transfer step in created status — the step exists to hold the money movement the authorization anticipates, but nothing has happened on-chain yet, so transaction_hash and confirmed_at are both null.
The sending account is necessarily absent at this point because the transaction has not happened yet:
estimated.from.account_idisnull. The originator VASP tells Tesser who is sending, not which wallet they will send from, so there is no sending account to reference until funds land. It is never pre-filled with a stand-in.estimated.*.networkis populated. Tesser only authorizes supported CAIP-19 assets, so the declared token identifies the network before funds move.
expires_at is null, as it is on every inbound payment.
Beneficiary Name Match
Once Tesser receives the Travel Rule data from the Originator VASP, it will check whether the Beneficiary name sent by the Originator matches the name your organization recorded for the counterparty owner of the destination wallet account. In other words, does your organization and the Originator VASP agree that "John Smith" owns the destination wallet, or does the Originator VASP think a different entity owns the destination wallet?
If the check passes, Tesser will return an "authorization" response to the Travel Rule data provider. If the check fails, Tesser will instruct the Travel Rule provider to reject the inbound payment.
The result of the check is visible on the participants[]. The BENEFICIARY entry in participants[] will be EXACT_MATCH for an authorized request, DID_NOT_MATCH for one Tesser rejected on the beneficiary-name match.
Code
When funds subsequently land on-chain, Tesser matches the on-chain transaction to the payment record and moves the step from created to confirmed status. That transition fires step.confirmed followed by payment.updated webhooks, which carry the step's new status, transaction_hash, confirmed_at, and the now-known estimated.from.account_id. From that point the payment follows the same path as a direct on-chain transfer.
step.confirmed is specific to this entry path. A direct on-chain transfer never fires it, because its step is already confirmed when the record is created — there is no transition to report.
Direct On-Chain Transfer
For a transfer that arrives with no Travel Rule data, payment.created fires when Tesser sees the transaction in a block. The step is already in confirmed and the estimated overlay is fully populated at both the top level and the step level, estimated.from.account_id included — the sending address is on the transaction, so the sending account is known from the outset. participants[] and provider_metadata[] are empty, because both depend on the screening result, and the actual overlay is present with all sub-fields still null.
Code
Inbound Payment Risk Review (payment.risk_updated)
Tesser screens the sending wallet against your organization's risk policy and reports the outcome on the payment as risk_status via a payment.risk_updated webhook. Screening runs on every inbound payment, whether or not a Travel Rule authorization flow preceded it. See Wallet Risk Screening Data for how to read risk_status_reasons[], and Wallet Screening Risk Categories for the category values.
For a payment that arrived as a direct on-chain transfer, the payment.risk_updated payload is also where participants[] first populates: identifying who the sending wallet belongs to depends on the screening attribution, so the two land together. The sending account is not part of this — estimated.from.account_id was already populated when Tesser recorded the transaction.
Risk screening runs in parallel with on-chain finalization, so payment.risk_updated and step.completed can fire in either order.
If your policy requires manual review, an initial payment.risk_updated fires with risk_status: "awaiting_decision". Optionally submit a decision via the risk review decision API or in the Tesser dashboard. If a decision is recorded, risk_reviewed_by and risk_reviewed_at are populated and a second payment.risk_updated fires with risk_status transitioned to manually_approved or manually_rejected.
Example webhook payload when the sending wallet is automatically approved per your organization's policy. The sending wallet was attributed to a VASP whose name did not exactly match an existing counterparty, so a new counterparty was created for it; the beneficiary carries USER_SUPPLIED_ID because no Travel Rule data accompanied the transfer and there was no supplied name to match:
Code
Inbound Payment Completion (step.completed and payment.updated)
When the block finalizes, several things occur:
- The step status transitions from
confirmedtocompleted. - The step-level and top-level
actualoverlays populate. - Tesser fires
step.completedfollowed bypayment.updatedwebhooks.
Finalization does not move the balance. The receiving wallet was credited earlier, when the transaction was confirmed in a block — see Spending the Inbound Funds. What finalization changes is that the credit can no longer be reversed.
The payment.updated payload below shows a terminal state in which risk was manually approved before the block finalized. Because screening and finalization are independent, step.completed can fire first — in that case this payload's risk_status will still be "unchecked" or "awaiting_decision", and participants[] will still be empty for a direct on-chain transfer. The actual overlay is unaffected either way: it mirrors the settled transaction, including actual.from.account_id, and does not wait on the screen.
Code
Spending the Inbound Funds
The receiving wallet's available and total balance are credited when the step reaches confirmed — that is, as soon as the transaction is in a block, not when the block finalizes.
We nevertheless recommend waiting for the step to reach completed before spending inbound funds. Do not attempt on-chain transfers, rebalances, payouts, or off-ramps to fiat against a credit that is only confirmed. Until the block finalizes the transaction can still be reorganized away, and if that happens Tesser reverses the credit — leaving you having spent against funds that no longer exist. Finalization is what makes the credit permanent.
The credit lands on the receiving managed wallet's matching asset — the entry whose currency and network match the payment. Fetch it with GET /v1/accounts/{estimated.to.account_id}:
Code
Balances are returned in major units, the same convention the payment's estimated and actual amounts use, so a balance and a payment amount are directly comparable.
The balance is credited regardless of the risk outcome — risk screening does not hold the funds. We recommend, separately from the finality point above, that you wait for an approving risk_status (automatically_approved or manually_approved) before spending inbound funds, and reconcile any automatically_rejected or manually_rejected outcome against your own compliance policy before putting the funds to use.
Why Availability Isn't Gated on the Screening Outcome
The credit tracks the chain, not the screening outcome. Tesser credits the wallet when the transaction lands and reverses that credit only if the chain itself takes the transaction back; risk_status never holds funds. That is a deliberate choice: if Tesser withheld a rejected payment from available balance, the funds would be stranded in the receiving wallet with no way to act on them, because an on-chain transfer cannot be sent back automatically. Crediting the balance preserves your ability to respond to a rejection — returning the funds to the originator, say, or moving them to a dedicated wallet to quarantine them. Your organization's compliance policy should determine what to do with the funds after rejection.
Do not naively return funds to the sender's address
The sending wallet may not be a self-hosted wallet under the original sender's control. It could be an exchange hot wallet, a smart-contract address, or any other on-chain address — the participants[] array will tell you when the sender was attributed to a hosting VASP (ORIGINATOR_VASP) or to a contract (CONTRACT). Returning funds to such an address could deposit them into a pooled exchange account or a contract from which they cannot be recovered. If you need to return funds, coordinate the return address with the sender out of band rather than reusing the sender account's crypto_wallet_address (retrievable by looking up estimated.from.account_id via GET /v1/accounts/{account_id} — use estimated, not actual, since you may be acting on a rejection before the block finalizes).
Failure Modes for Inbound Payments
An inbound payment fails in one of two ways, and both look the same on the wire: the step transitions to failed, step.failed fires, payment.updated follows with the failed step, and the funds do not remain in the receiving wallet. What distinguishes them is how far the payment got. A chain reorganization fails a step that had reached confirmed — there was a transaction, it was credited, and both were undone. A rejected authorization or a sender who never broadcasts fails a step still in created — no transaction ever existed and nothing was ever credited. Read the step's prior status and its status_reasons to tell which occurred.
Chain Reorganization
If the block containing the on-chain transaction does not finalize — most commonly because a chain reorganization removes the transaction from the canonical chain — the inbound payment terminates and the credit made at confirmation is reversed. This is the case the "wait for completed" recommendation exists for: the funds were in the wallet's available balance, and then they were not.
What you will observe:
step.failedfires. The step transitions fromconfirmedtofailed,failed_atpopulates, and step-levelstatus_reasonscarries the failure detail.payment.updatedfires alongside, carrying the full updated payment with the failed step.- Top-level
actual.*stays all-null because nothing settled.estimated.*remains populated from record creation, preserving the original observation;desired.*is all-null as always for inbound. - Risk screening is unaffected. A reorganization fails the step; it does not cancel a screen in progress or suppress its result. A screen that was still running completes normally and
payment.risk_updatedfires as usual, and a screen that had already completed keeps itsrisk_status,risk_status_reasons[], andWALLET_RISK_SCREENINGentry inprovider_metadata[]on the failed payment. The screening outcome describes the sending wallet, which does not become less true because the transaction was undone. For a payment that came through a Travel Rule authorization flow,participants[]and theTRAVEL_RULEentry inprovider_metadata[]were written at record creation and are likewise preserved. - The credit is reversed. The receiving wallet's available and total balance return to what they were before the transaction was confirmed.
Example payment.updated webhook payload at the terminal failed state, for a payment that arrived as a direct on-chain transfer whose screening had not yet completed when the reorganization was detected. risk_status is unchecked and participants[] is empty at this moment only — the screen is still running, and a later payment.risk_updated will carry its outcome:
Code
Rejected or Never-Funded Travel Rule Records
A payment record created from a Travel Rule authorization request that Tesser rejected on the beneficiary-name match — or one whose funds simply never arrive — records the authorization outcome and the supplied identity data, but no money ever moves against it. The step fails rather than lingering: this is a terminal state, reached without any on-chain transaction.
- The step transitions from
createdtofailed,failed_atpopulates, and step-levelstatus_reasonscarries the reason.step.failedfires, followed bypayment.updated. Note the step never reachesconfirmed— that is what separates this from a chain reorganization, where the step wasconfirmedbefore it failed. estimated.from.account_id, the step'stransaction_hash,confirmed_at, and everyactual.*field staynull; there was never an on-chain transaction to reconcile. The declared amount, currency, network, and destination remain onestimated.participants[]and theTRAVEL_RULEentry inprovider_metadata[]are preserved as written at record creation. A rejected authorization is identifiable from theBENEFICIARYentry'sresolution: "DID_NOT_MATCH".risk_statusremainsunchecked— wallet screening runs against the sending wallet, which is only known once funds land, so it never runs for a record that is never funded. This is the one case whereuncheckedis terminal rather than transitional: the step reachingfailedis your signal that no screening outcome is coming.- The receiving wallet's balance is untouched. Nothing was ever credited, because the credit happens at
confirmedand this step never got there.
Inbound Payment Event Sequence
The happy path for an inbound payment fires the following webhook events. payment.risk_updated and step.completed run on independent clocks and can fire in either order; the receiving wallet's balance is credited at confirmed, before either of them, regardless of the risk outcome.
| Event | Fires when |
|---|---|
payment.created | Tesser creates the payment record, always with one transfer step and the estimated overlay populated at the top level and the step level. Travel Rule flow: fires when the authorization request is handled, before funds move; the step is in created, estimated.from.account_id is null, and the declared amount, currency, network, destination, participants[], and TRAVEL_RULE metadata are present. Direct on-chain transfer: fires when the transaction is included in a block; the step is already in confirmed, estimated.from.account_id is populated, and participants[] and provider_metadata[] are still empty. |
step.confirmed | (Travel Rule flow only.) Funds land on-chain and Tesser matches the transaction to the record created from the authorization request, moving the step from created to confirmed. A direct on-chain transfer does not fire this event — its step is already confirmed at payment.created, so there is no transition. |
payment.updated | (Travel Rule flow only.) Fires alongside step.confirmed, carrying the step's new status, transaction_hash, confirmed_at, and the now-known estimated.from.account_id. |
payment.risk_updated | The risk screening of the sending wallet completes. risk_status transitions to automatically_approved, automatically_rejected, or awaiting_decision. For a direct on-chain transfer the same payload carries the newly populated participants[] and the WALLET_RISK_SCREENING entry in provider_metadata[]. For the manual-review path, a second payment.risk_updated fires once a reviewer records a decision via the risk review decision API or in the Tesser dashboard, transitioning risk_status to manually_approved or manually_rejected and populating risk_reviewed_by and risk_reviewed_at. |
step.completed | The block finalizes. The step transitions to completed and the step-level actual overlay populates. |
payment.updated | Fires alongside step.completed. The top-level actual overlay populates to reflect the finalized amounts. For a Travel Rule flow this is the second payment.updated on the payment. |
The step's status is the shortest summary of where a payment stands: created (recorded, nothing on-chain yet) → confirmed (in a block) → completed (finalized), or failed from either of the first two. Every failure path shares one shape — step.failed followed by payment.updated, with the payment terminating without crediting funds — whether the cause is a chain reorganization, a rejected Travel Rule authorization, or a sender who never broadcasts. See Failure Modes for Inbound Payments for what distinguishes them on the record.
Reconciliation via the API
If your integration misses a webhook or needs to reconcile state on a schedule, fetch inbound payments via the Payments API:
Code
Auto-created unmanaged wallet accounts are also retrievable via GET /v1/accounts with the is_managed=false filter, and the counterparties they belong to via GET /v1/counterparties. See the Payments API reference, Accounts API reference, and Counterparties API reference for the full set of query parameters.