Skip to main content
A transaction is a quote carried out: Endl takes the money from the source you named and pays the recipient. Every transaction starts from a quote and gets a txn_… id when you submit it. Nothing moves before submit.
This is the conceptual guide. For parameters and schemas see Create pre-transaction, Submit transaction, Get transaction and List transactions.

Before you start

The flow

1

Price it

Generate quote returns a qut_… id. The ten-minute clock starts now and runs through submit.
2

Check it

Create pre-transaction checks the quote, recipient, wallet and KYC together. It creates nothing and moves nothing.
3

Attach an invoice (only if required)

Upload invoice returns file_… ids.
4

Submit

Submit transaction creates the txn_… and moves the money. This is the only call that does.
5

Track it

Webhooks tell you something changed; Get transaction tells you what.

Step by step

The example sends 100 USDC from a customer’s wallet on BaseSepolia to a VND bank account in Vietnam. Every call carries Api-Key and Api-Version: 2026-09.1.

Check it with a pre-transaction

Show source.amount and destination.amount to the user here for a final confirmation. Pre-transaction is safe to repeat for the same quote.

Submit

Submit answers 200 with the transaction object. Store txnId. For a wallet-funded transfer Endl sends from the wallet during this call, so the first status you see is often already IN_PROGRESS:
Trimmed: the full object also carries fees, recipientData and depositData. See Get transaction.

Picking endlTransactionMode

The mode must match the quote: a USDC → VND quote is STABLE_COIN_TO_FIAT.

Funding

You never send funds to a deposit address on this surface. You name the source, and Endl moves the money server-side when you submit. That is what settlement: "SERVER_SIDE" on the pre-transaction means.
Send walletId, never fromAddress. The wallet must be one of this customer’s active wallets; Endl reads its address itself. A missing walletId on a wallet-funded transfer is 400 ERRTXN_1002; a wallet that is not the customer’s, or not active, is 400 ERRTXN_1003. Submit checks this before anything is written.
The wallet must hold enough. The debit is source.amount from the quote, fees included. Check it with Get wallet balance first. On sandbox, new EVM wallets arrive with 100 test USDC and 100 test USDT on their network.

Statuses

A transaction carries status for where it is and subStatus for the step inside it. Decide on status, but always read subStatus when the status is IN_REVIEW: a failure or refund can show up there first.
There is no PENDING, PROCESSING, COMPLETED, FAILED or CANCELLED status. Match the values above exactly, and treat an unknown value as “not final yet”.

Webhooks

The event’s data.id is the txn_… id and its payload carries status, sourceCurrency and destinationCurrency. Nothing is sent for IN_REVIEW or PENDING_TRAVEL_RULE, and a fast transfer can finish during submit without a separate updated event. On every event, call Get transaction for the full state, and poll it for transfers that go quiet. See Webhooks for signing and delivery.

Purpose codes, invoices and listing

Purpose codes. Every transfer except TOP_UP needs transferPurpose and transferPurposeCode. Take the code from List purpose codes; pass relation=SELF when the customer pays themselves. Invoices. When a purpose code or recipient has invoiceMandatory: true, upload the invoice before submit and pass the ids in invoiceFileIdList. Upload takes multipart/form-data with part name file, 1–5 files, 2 MB each. Listing. List transactions is a POST because the filters travel in the body. The response is { "txns": [ … ], "totalCount": 42 }; totalCount counts every match, not just this page.

Errors and retries

Errors use the envelope described in Errors: branch on errors[0].code, not the top-level message.
A quote backs one transaction, ever — including one that failed. Resubmitting the same quoteId is refused, so a retry after a failed submit starts with a new quote. There is no Idempotency-Key header: the single-use quote is what stops a double payment.
If submit times out, do not resubmit straight away. List transactions with sortBy: "createdOn,desc" and look for your quoteId. If it is there, the submit went through; track it. If not, get a fresh quote and start again.

Common pitfalls

Wallet-funded modes need walletId on both pre-transaction and submit. Do not send fromAddress; it is not a field on this surface.
Quotes are single-use, and the first attempt consumed this one. Generate a fresh quote for every attempt.
No event is sent for IN_REVIEW. Read subStatus: TRANSACTION_REJECTED or TRANSACTION_REFUNDED there is the outcome.
sourceAmount, destinationAmount, fxRate and chargesTotal are decimal strings on the transaction object. Parse them as decimals, not floats.
KYC depth follows the route. A Bridge-routed transfer needs the full Bridge check even when a stablecoin → stablecoin transfer works with Sumsub alone.