Create a Cross-Ramp Payment
For certain payment corridors, you can deliver a fiat currency to a provider and have that provider pay out directly to a beneficiary in a different local currency. This guide describes how to create and execute a cross-ramp payment.
How the cross-ramp differs
The provider handles conversion from the "from" fiat currency to the "to" fiat currency. As a result:
- No step is signed — there is no on-chain transfer, so your organization never signs a wallet step.
- No on-chain risk screening. Funds are not sent on chain, so there is no wallet to screen.
risk_statusremainsuncheckedand nopayment.risk_updatedwebhook fires. - No balance reservation. Likewise, because no Tesser-held balance is reserved,
balance_statusstaysnulland nopayment.balance_updatedwebhook fires.
The originator funds the cross-ramp from the source-currency virtual account they hold with you. How that account is itself funded is outside Tesser.
Workflow
A cross-ramp payment progresses through these phases:
- Create the payment and request an FX rate quote —
POST /v1/paymentswith the currency pair. Receive a payment ID synchronously. Tesser requests a cross-ramp quote from the provider and fires a singlepayment.quote_createdwebhook carrying the end-to-end FX rate and the two plannedsteps[]. - Submit accounts and lock the rate —
PATCH /v1/payments/{paymentId}with the originator's source-currency account (desired.from.account_idandfunding_account_id) and the beneficiary's destination-currency account (desired.to.account_id). This attaches the accounts and instructs Tesser to create the cross-ramp with the provider, locking the quoted rate. Tesser fires apayment.updatedwebhook. This PATCH must happen before the quote expires. - Fund the cross-ramp — the originator pushes the exact source-fiat amount into the provider's source-currency virtual account (step 1). Tesser observes receipt and step 1 completes.
- Provider converts and pays out — the provider converts the source fiat to the destination fiat and pays it out to the beneficiary's bank (step 2).
- Reach terminal state — the payment's
actualoverlay populates and a finalpayment.updatedwebhook fires alongside the laststep.completed.
The Two-Step Model
A cross-ramp payment is one Payment resource whose steps[] array contains two funds-transfer steps. The table below summarizes what your organization sees; the per-step sections later in this guide walk through each in detail.
| Step | Description | Step type | Initiated by | You sign? |
|---|---|---|---|---|
| 1 | Fiat transfer of the source currency from the originator's virtual account to the cross-ramp provider's source-currency virtual account | transfer | Originator (pushes source fiat via the local push-payment rail) | No |
| 2 | Fiat payout of the destination currency from the cross-ramp provider's destination-currency virtual account to the beneficiary's bank account | transfer | Cross-ramp provider | No |
The provider converts the source currency to the destination currency between these two steps — inside the provider, moving funds from its source-currency virtual account to its destination-currency virtual account. That conversion is not surfaced as a swap step, which is why step 1 is denominated entirely in the source currency and step 2 entirely in the destination currency. The end-to-end rate is carried on the payment-level estimated overlay. See Steps.
Example Scenario
The remainder of this guide walks through a concrete example: a cross-ramp payment from Mexico to the United States.
Phase 1: Request an FX Rate Quote
When the originator initiates a payment request, your organization creates a Payment at Tesser to request an end-to-end FX rate quote. The POST /v1/payments call supplies the currency pair and one side of the amount; account fields are deferred to the PATCH in Phase 2. There is no separate quote API endpoint. Quotes are obtained by creating a payment record.
In this example the originator specifies the USD amount the beneficiary should receive, so the POST body sets desired.to.amount and leaves desired.from.amount null.
Code
Tesser responds synchronously with the new Payment record. At this point the desired overlay echoes what you submitted; estimated.*, actual.*, and steps[] are all empty or null because Tesser has not yet requested the cross-ramp quote from the provider.
Code
Tesser then requests a cross-ramp quote from the provider to compute the end-to-end cost and FX rate, and fires a single payment.quote_created webhook carrying the MXN amount the originator needs to fund (estimated.from.amount) and the two planned steps.
The accounts that the cross-ramp provider has established for the originator are known at planning time, so step 1's to.account_id (the provider's MXN virtual account) and step 2's from.account_id (the provider's USD virtual account) are populated on the quote. The originator's and beneficiary's account IDs remain null until you attach them in Phase 2.
Code
Fees and FX rates
The ratio of estimated.from.amount to estimated.to.amount is Tesser's all-in end-to-end FX rate. A few things to keep in mind:
- Surface this rate (with any markup you apply) to the originator in your UI. If the originator accepts, continue to Phase 2 to lock the rate; if they decline, don't execute the payment.
- The provider's costs are embedded in the quoted MXN amount (
estimated.from.amount). This includes the payout rail fee (if applicable). Tesser requests enough destination currency from the provider so that the beneficiary still receives the fulldesired.to.amountin thedesired.to.currency. - Cross-ramp payments can be created 24/7/365. However, open market FX rates from the provider are available only Monday to Friday, 8:00 AM – 3:00 PM (CST). Transactions created outside open market hours will have an additional cost.
- Tesser's fee is separate. For cross-ramp payments, Tesser's fee is invoiced separately and does not appear as a step
fees[]line item in this flow.
The provider holds a quoted rate for only about 10 seconds — the window in which you must lock it by executing Phase 2 (PATCH). This is not the funding deadline: if the 10-second window lapses before you PATCH, request a fresh quote by creating a new payment. The payment's expires_at is a separate, later deadline — the cutoff by which the originator must fund the cross-ramp once the rate is locked (see Phase 3).
Phase 2: Submit Payment Details and Lock the Rate
Once the originator accepts the rate, your organization populates the account identifiers via a PATCH call. This finalizes the payment's desired overlay and instructs Tesser to create the cross-ramp with the provider, locking the quoted rate.
desired.from.account_idandfunding_account_idboth reference the originator's MXN virtual account issued by your organization — the account from which the originator pushes the source fiat in step 1.desired.to.account_idis the beneficiary's US bank account, where the provider delivers the USD in step 2.- The
payment_infoobject carries optional detail about why the originator is sending this payment. Both of its fields are optional:payment_description_wire— free text to appear on the beneficiary's bank account statement (e.g. an invoice number).payment_reason_code— a short code classifying the purpose of the payment. A standardized set of accepted values will be published in a future release; you can omit it today.
Code
The PATCH succeeds synchronously and Tesser publishes a payment.updated webhook reflecting the populated account IDs on the desired overlay and on the affected steps. The provider's own virtual-account IDs were already populated at quote time.
Code
Phase 3: Fund the Cross-Ramp (Step 1)
With the rate locked, fund the cross-ramp by pushing funds from the MXN virtual account your organization has issued to the MXN virtual account issued by the provider for the originator. The originator must fund before the payment's expires_at — the provider's funding cutoff — or the payment expires (see Failure Modes). Step 1 stays created until the provider observes the incoming funds; step 1 transitions created → completed when the provider notifies Tesser of MXN receipt.
For payments created during business hours (after 8am CST Monday through Friday), Tesser sets the funding cutoff (expires_at) as 1:30pm CST to ensure same-day delivery of funds to the beneficiary.
Payments created after 1:30pm CST are treated as though they were created the next working day.
Look up the provider's deposit instructions
Retrieve the provider's MXN virtual-account details from Tesser. Step 1's estimated.to.account_id — carried on both the payment.quote_created and payment.updated webhooks — holds the Tesser identifier for the provider's MXN virtual account. Call Get an account by ID with that account ID to retrieve the bank account information needed for the SPEI push. The account number is returned masked by default. Pass include_secure=true on the Get an account by ID request to retrieve the full account number required to complete the push.
Step 1's actual overlay reflects the amount sent to the provider.
Code
Phase 4: Provider Converts and Pays Out (Step 2)
Once the MXN lands in the provider's MXN virtual account, the provider converts it to USD and pays the USD out to the beneficiary's US bank account. Step 2 is driven entirely by the provider; your organization does not sign for it or initiate it.
Code
submitted_at populates when Tesser marks the payout step submitted; confirmed_at when the provider confirms the destination-side transfer is underway; completed_at when the provider settles the USD to the beneficiary's bank. Step 2's from side is denominated in USD (out of the provider's USD virtual account) and its to side delivers USD to the beneficiary — the MXN→USD conversion was absorbed inside the provider between step 1 and step 2.
Code
Terminal State
When step 2 completes, Tesser populates the payment's top-level actual overlay and fires a final payment.updated webhook. On success, actual.to.amount matches desired.to.amount (the beneficiary receives exactly the USD the originator requested) and actual.from.amount matches the MXN the originator funded against the locked rate.
Failure Modes
When a payment reaches a terminal outcome — both steps completed, or a step failed along the way — Tesser populates the top-level actual overlay once and never overwrites it, and fires a payment.updated webhook carrying it (on success and on failure alike). Failed steps always have all-null actual.*.
Incorrect MXN amount funded
If you send more or less than the quoted estimated.from.amount:
- The payment is not automatically failed.
- Step 1's
actualoverlay reflects the amount the originator actually sent, and step 1 remains increatedwhile the mismatch is reconciled manually between your organization, Tesser, and the provider (for example, refunding an overpayment or collecting a shortfall). - Once the mismatch is resolved, the payout is delivered.
Contact Tesser support if a payment remains in created longer than expected.
Originator funds never arrive
If the originator never pushes the MXN, step 1 cannot complete. If expires_at is reached without funds arriving, the payment expires: both steps transition to failed carrying the transfers-9204 reason (The transfer expired before it could be executed). There is no dedicated payment.expired event; the expired state is reflected in the payment's terminal actual overlay.
Payout failure or compliance rejection
If the cross-ramp provider cannot complete the payout — for example, a compliance rejection or a receiving-bank issue — step 2 transitions to failed. Treat a stalled or failed step 2 as a customer-service event and contact Tesser support.
Webhook Events by Phase
The following table summarizes the webhook events fired through the lifecycle of a successful cross-ramp payment.
| Phase | Webhook event | Carries |
|---|---|---|
| Quote | payment.quote_created | End-to-end FX rate via estimated.from.amount, plus the two planned steps[] (both status: "created"); the provider's virtual-account IDs populated, originator/beneficiary accounts null |
| PATCH | payment.updated | Account IDs populated on the desired overlay and attached to step 1 (from) and step 2 (to); the cross-ramp is created with the provider and the rate is locked |
| Step 1 | step.completed | The provider notified Tesser that the originator's MXN push landed in the provider's MXN virtual account |
| Step 2 | step.submitted → step.confirmed → step.completed | The provider converted the MXN and paid out USD: submitted_at when Tesser marked the payout step submitted, confirmed_at when the provider confirmed the payout underway, completed_at when the USD settled to the beneficiary's bank |
| Terminal | payment.updated | Top-level actual overlay populated; payment in terminal state |