Simulate a Travel Rule Inbound Payment
POST /v1/payments/simulate-travel-rule-inbound has a Tesser-operated simulator VASP originate a real Notabene Travel Rule transfer into one of your managed wallets, so you can test your integration's handling of an inbound Travel Rule exchange without standing up a second VASP.
Everything about the compliance decision is genuine: a real Notabene transfer is created, real PII is exchanged, and your organization's own name-matching and authorization logic decides the outcome — the same code path a real counterparty-initiated inbound runs through. Only the counterparty is simulated.
The response arrives before the money does
Where the transfer is authorized, real testnet USDC is broadcast — but not before this endpoint answers. The response comes back as soon as the payment exists, with its step created and transaction_hash: null. The broadcast follows a few seconds later, and the step then confirms and credits on its own. A 201 with an unconfirmed step is the expected first state, not a stuck one.
Prerequisites
- Sandbox or staging only. The endpoint is unavailable in production, gated the same way as
simulate-inbound. Calling it in production returnspayments-3053. - The
notabene-travel-rule-inboundfeature flag must be enabled for your organization. Without it, the webhooks that carry the compliance decision are received and dropped, so the call would otherwise hang until it times out — the endpoint checks the flag up front instead and fails fast withpayments-3083. Contact Tesser support to have the flag enabled for your organization. - Your organization must have its Travel Rule VASP identity configured with Tesser. This is set up during onboarding; if it is missing, the call fails with
payments-3085. to_accountmust be a managedstablecoin_ethereumwallet in your workspace. Creating a managed wallet registers it with Notabene automatically — no separate registration step. An account that does not exist, isn't managed, or isn't astablecoin_ethereumwallet fails the call (payments-1002/payments-3055).
You never see or supply any Notabene credentials — the simulator VASP's identity and your organization's own Travel Rule identity are both handled server-side.
See Authentication for how to obtain the bearer token ($CLIENT_TOKEN below).
Request
| Field | Required | Description |
|---|---|---|
to_account | Yes | The managed wallet account ID the simulated counterparty claims to be paying. |
claimed_beneficiary_name | No | The name the simulated counterparty claims for the beneficiary. Omit it to have the simulator claim the destination account owner's own name — the transfer is authorized. Supply a name that doesn't match, and Tesser's own name-matching rejects the transfer, which is how you rehearse your refusal path. |
network | No | One of BASE_SEPOLIA, ETHEREUM_SEPOLIA, POLYGON_AMOY. Defaults to BASE_SEPOLIA. |
presentation | No | full or partial — how completely the simulated counterparty satisfies your presentation policies, see The partially satisfied presentation. Defaults to full. |
mocked_risk_status | No | Forces the risk_status of the screening that runs once the simulated money arrives — see Forcing a risk verdict. Omitted, the inbound is screened for real. |
The simulated payment books 1 USDC, except on ETHEREUM_SEPOLIA, where it books 3500 USDC: our Travel Rule provider prices Sepolia USDC itself, so only a transfer whose own value clears the US$3,000 threshold is flagged Travel Rule there. The other two testnet USDCs are unpriced, so they flag on the declared value alone and the amount stays small. On the authorized branch that exact amount is what gets broadcast.
The authorized call
Omit claimed_beneficiary_name and the simulator claims the destination wallet owner's actual name, so the name match succeeds and the transfer is authorized:
Code
As soon as the compliance decision lands and the resulting payment exists, the endpoint responds 201:
Code
travel_rule sits alongside the payment fields inside data — it is not a separate resource. It carries only what is known at the moment the payment ID is returned:
| Field | Description |
|---|---|
transfer_id | The Notabene transfer ID the simulator created. It is not a Tesser payment ID and cannot be passed to GET /v1/payments/{id}, nor filtered on — see The 202 response for how to find the payment it becomes. |
is_travel_rule | Whether Notabene flagged the simulated transfer as Travel Rule. Notabene sets this shortly after the transfer is created; false means either it never did within the wait budget or reading the transfer was permanently refused — either way, no compliance decision will follow and no money will be sent. |
presentation | Which rehearsal ran, echoing the request: full or partial. |
presentation_answered | Whether the simulated counterparty's PII reached your organization's Travel Rule policy. false means the PII exchange itself failed — no name-matching decision was made, and no money will be sent. |
The decision has already been made by the time you get a 201 — the payment exists because it landed. The response simply does not carry its details, and the transaction hash and confirmation genuinely do follow later. All of it is read from the payment:
| What you want | Where to read it |
|---|---|
| The Travel Rule decision | steps[].status on GET /v1/payments/{id} — created where the transfer was authorized, failed where it was rejected |
| The raw Notabene transfer | provider_metadata[], under provider_metadata_key: "NOTABENE" — provider data as received, whose own status can lag the decision (see The partially satisfied presentation) |
| The beneficiary name match | participants[] — score, threshold and resolution |
| The transaction hash | steps[].transaction_hash |
| Confirmation and the credit | steps[].status, and the payment.updated webhook |
201 is not proof of authorization
A 201 response means the compliance round trip finished — it does not mean it finished with authorization. A rejected transfer also produces a payment here, with its step failed rather than created. Read steps[].status for the actual decision — never the HTTP status code alone, and not the transfer's own status inside provider_metadata, which is raw provider data and is not updated for every decision Tesser takes.
What happens after the response
For an authorized transfer, in order, over roughly the next few seconds:
- Tesser broadcasts the payment's booked amount in test USDC from the platform faucet to your managed wallet.
- The simulator settles the Notabene transfer with that real transaction, and Notabene's settlement webhook stamps the hash onto the payment's pending step.
GET /v1/payments/{id}now shows atransaction_hashon a step stillcreated. - The transfer is detected on chain, matched to this payment, and the step moves to
confirmed— the balance is credited and screening runs.
Steps 2 and 3 are the same code a genuine counterparty-initiated inbound runs through; nothing about them is specific to the simulator. If you subscribe to webhooks, you will see payment.updated as the step confirms.
A rejected transfer stops before step 1: no money is sent, and the step stays failed.
The refusal call
Supply a claimed_beneficiary_name that does not match the destination wallet's owner, and Tesser's own name-matching logic refuses the transfer — the same decision it would make against a genuine counterparty:
Code
The simulated counterparty claims that wrong name as the beneficiary. Your organization's name-matching compares it against the destination account owner's actual name, does not find a match, and rejects the transfer. The response is still 201 — the compliance round trip finished, it just finished with a rejection:
Code
The step's status is failed, not created, and no money is ever sent for it — a refusal is where the simulation stops. See Payment Participants for how a rejected Beneficiary name-match generally surfaces on a payment's participants[].
The 202 response
The compliance round trip — Notabene flagging the transfer as Travel Rule, the PII exchange, and your organization's name-matching decision — can take longer than the request's wait budget (about 40 seconds). When it does, the endpoint responds 202 with what it knows so far, rather than blocking further:
Code
A 202 does not mean the simulation failed, and it does not affect the money. The decision is still coming; when it lands, an authorized transfer is still broadcast and still credits your wallet exactly as it would have on the 201 path. All you have lost is the payment ID in the response.
is_travel_rule: false means either Notabene never flagged the simulated transfer as Travel Rule within the wait budget or reading the transfer was permanently refused, so no decision — and no payment, and no money — will ever follow for that transfer. presentation_answered: false means the simulated counterparty's PII never reached your organization's policy, and likewise nothing further will happen.
Do not re-issue the same request to find the earlier payment: every call creates a brand-new Notabene transfer and a new simulated inbound, so retrying can never surface the one you are waiting on, only add another — and on the authorized path it sends a second lot of money. There is also no supported way to look the earlier transfer up by ID — GET /v1/payments's filters (start_date, end_date, direction) don't include a transfer or provider-reference filter. Once the compliance decision lands, get the resulting payment one of two ways:
- Subscribe to the
payment.createdwebhook, if you receive webhooks in this environment, and read the payment ID off the event. - List inbound payments (
GET /v1/payments?direction=inbound) and match thetransfer_idyou got from your original request or202response againstprovider_metadata[].provider_metadata_data["@id"]on each result yourself.travel_ruleis only present in this endpoint's own response —GET /v1/paymentsdoes not return it.
The partially satisfied presentation
A real counterparty does not always answer everything you ask of it. Where your organization raises more than one presentation policy on a transfer, the counterparty's PII can satisfy one and leave another open — Notabene then reports the presentation as partially satisfied rather than complete. Pass presentation: "partial" to rehearse that:
Code
Two presentation policies go up on the transfer instead of one, and the simulator answers only the second in full. The first stays open — it asks for a set of fields no PII body can satisfy — so Notabene evaluates the exchange as partially satisfied. It cannot be produced by sending thinner PII: Notabene refuses an IVMS101 body that omits anything the policy asks for, so the split has to be between policies, not within one.
Tesser still authorizes on the strength of the beneficiary name match alone, and still records the payment, exactly as it does on the full path — a partially satisfied presentation is not a rejection. What changes is what your integration sees on the way there, which is the point of rehearsing it. One difference to expect: with a policy left open, Notabene never moves the transfer's own status past INCOMING, so the copy under provider_metadata keeps saying INCOMING on a payment that was authorized. The decision is on steps[].status, as always. claimed_beneficiary_name composes with this: supply a name that does not match and the transfer is refused on the name, partial presentation or not.
Forcing a risk verdict
Because the authorized branch produces real inbound money, that money is screened like any other inbound. Pass mocked_risk_status to force the verdict instead of letting screening run for real:
Code
Accepted values are automatically_approved, awaiting_decision and automatically_rejected — the same set simulate-inbound takes. The verdict is applied when the broadcast is detected on chain, so it appears as risk_status on the payment a few seconds after the response, not in the response itself.
This is what lets you rehearse a combination neither simulator could produce on its own: a transfer your Travel Rule policy authorizes whose money your risk screening then rejects. On a rejected transfer the field has no effect, because no money is sent and nothing is screened.
simulate-travel-rule-inbound vs simulate-inbound
Both simulators now produce a credited inbound payment. They differ in what they let you control on the way there:
simulate-travel-rule-inbound | simulate-inbound | |
|---|---|---|
| Simulates | A Travel Rule counterparty, and the money that follows | The money |
| Counterparty | A real Notabene transfer from a Tesser-operated simulator VASP | N/A — funds come from the platform faucet |
| On-chain transfer | On the authorized branch only, of the payment's booked amount | Always, a fixed 1 USDC |
| Balance change | On the authorized branch, once the transfer confirms | Yes, once the transfer confirms |
| Pending step | created, then confirmed — or failed on a rejection, with no money sent | created, then confirmed |
| What you control | claimed_beneficiary_name to force authorization or rejection, presentation for a partially satisfied exchange, and mocked_risk_status | mocked_risk_status |
Reach for simulate-inbound when you only care about inbound money arriving and want the cheapest, fastest path to it. Reach for this endpoint when the Travel Rule exchange is part of what you are testing.
Errors
| Error Code | HTTP Status | Meaning |
|---|---|---|
payments-1002 | 404 | to_account did not resolve to an account in this workspace. |
payments-3053 | 400 | Simulation is not available in production. |
payments-3054 | 400 | An unsupported network value, from an internal configuration gap rather than the request itself — a network outside BASE_SEPOLIA, ETHEREUM_SEPOLIA, POLYGON_AMOY is rejected by request validation before this error can fire. |
payments-3055 | 400 | to_account exists but is not a managed stablecoin_ethereum wallet with an on-chain address. |
payments-3083 | 400 | The notabene-travel-rule-inbound feature flag is not enabled for your organization. Contact Tesser support to have it enabled. |
payments-3084 | 503 | The simulator VASP is not configured in this environment. This is an environment issue on Tesser's side, not something a request can fix. |
payments-3085 | 400 | Your organization has no Travel Rule VASP identity configured with Tesser. |
payments-3086 | 400 | The destination account resolved to no usable beneficiary name to claim. Supply claimed_beneficiary_name, or give the account's entity a name. |
See the Errors reference for the full Payments error table, and Compliance and Risk Management for how Travel Rule and wallet risk screening apply across all payment directions.