Explainers

Stablecoin API data model: customers, accounts, rates, transactions

A stablecoin API data model links customers, wallets, accounts, destinations, rates and transactions. Objects, states and IDs to store, as of November 1, 2026.

Caio Barbosa

Founder & CO-CEO

Forbes Under 30. One of the leading voices in Fintech & Crypto in Brazil. Writes weekly about stablecoins, payments, and the future of financial infrastructure in Latin America.

Cover image for Lumx blog article: Stablecoin API data model: customers, accounts, rates, transactions
Cover image for Lumx blog article: Stablecoin API data model: customers, accounts, rates, transactions

A stablecoin API data model is the set of objects a payments API uses to describe a money movement and how they connect: a customer who owns value, the wallets and accounts that hold it, the destinations that receive it, a rate that prices a conversion and a transaction that executes it. Webhook events report each status change.

This post covers the objects and how they connect. The category is defined in what a stablecoin API is, and the steps to go live are in the seven-step integration guide. The names below are Lumx's, read from the Lumx docs and its OpenAPI file (OpenAPI 3.1.0) on November 1 and 2, 2026.

Which objects does a stablecoin API data model contain, and how do they connect?

Seven objects carry almost every flow, plus the event that reports changes on them. Each answers one question about a payment. Everything hangs off a customer, and the money movements sit beside that tree and point back into it: a transaction names its customer, an off-ramp names its destination, and a locked transaction names its rate.

The seven objects, the question each answers, their name on Lumx and what each is attached to.

Object

Question it answers

Lumx name and endpoint

Attached to

Customer

Who is the legal party?

customer, /customers

nothing, it is the root

Wallet

Where does stablecoin sit?

wallet, inside the customer record

customer, one per blockchain

Account

Where does fiat arrive and leave?

account (virtual account), /accounts

customer, one currency each

Destination

Where does a payout land?

destination, /destinations

the paying customer

Rate

At what price does a conversion run?

exchange rate, /exchange-rates

customer, optional on a transaction

Transaction

What moved?

transaction, /transactions

customer, plus a destination or rate

Partner fee

Who earns what on top?

partner fee, /partner-fees

optional on a rate or transaction

A destination belongs to the customer who pays, so the same bank account paid by two customers is two destination records.

On Lumx, every object except the wallet has its own endpoint and a UUID, and the wallet lives inside the customer record.

A customer is the legal owner of balances and history, and it carries its own compliance status. Your own company is a customer when you move your own money, and every user or business you onboard is another. One company with five admins is one customer. Mapping logins to customers one to one duplicates verification and splits payments across records of the same legal party. Until the party is verified, nothing it owns can move. What KYB is covers it for companies, and KYC (know your customer, the verification of an individual) is the same idea for people.

On Lumx, a customer is an individual or a business, and verification has five statuses: NOT_STARTED, UNDER_VERIFICATION, APPROVED, FINAL_REJECTION and RFI (request for information, a compliance hold that asks for a document before a transaction clears). RFI means the submitted information was invalid or inconsistent, and the customer can resubmit. A business customer also carries associated parties, the owners and representatives, each created as a record under it.

Lumx needs to see every payer and every payee. A platform cannot pool funds for people who were never onboarded, so each end user or seller is a customer of their own. The docs call this nested payments.

Wallets hold stablecoin, accounts hold fiat

Value can sit in two places, and they are different objects. A wallet is an address on a blockchain with a balance per stablecoin. An account is a fiat account in the customer's name, tied to one currency and issued through a banking partner. A wallet exists or it does not, while an account passes through provisioning and can pause for more information.

On Lumx, creating a customer provisions a wallet on every supported blockchain: Ethereum, Polygon, Base, Tron and Stellar. Wallets are custodial (what a custodial wallet is, and the custodial wallets product), and they are enabled once the customer is APPROVED. The wallets array only appears on customer responses from that point. Each wallet has a blockchain, an address, an isDefault flag and balances in USDC or USDT.

An account is created with its customer and has seven statuses: AWAITING_ONBOARDING, REQUESTED, PROVISIONING, RFI, ACTIVE, REJECTED and INACTIVE. BRL, MXN, COP, EUR and GBP accounts activate right after the customer is approved, and USD takes up to one business day because the banking partner reviews first. The local account number is a named virtual account (a local account number issued in the customer's own name), explained in what a named virtual account is and on the named virtual accounts page. The account.active webhook says when it can receive funds.

Destinations make the payee part of the model

A destination is where fiat lands when a payout completes: the bank details, the holder, and the relationship between the holder and the customer who pays. Model it as its own record under the paying customer, never as a field on a payee profile. In business payments the payee is rarely the payer, so a schema with one party on both sides breaks the first time someone pays a supplier.

On Lumx, you state the holder relationship when you create a destination. The values are SELF for the customer's own account, plus twelve third-party values such as SUPPLIER, EMPLOYEE and SUBSIDIARY_COMPANY. A transaction with the PERSONAL_ACCOUNT purpose can only go to a SELF destination. Each destination goes through its own verification, reported as destinations.under_verification, destinations.approved or destinations.final_rejection events. The rails a destination can use depend on its country and currency, listed on the coverage page.

A quote is a rate with an expiry, and a fee rides on it

A conversion needs a price, and an API can offer two kinds. A reference rate is a number for your screen and promises nothing. A locked rate is an object with an ID and an expiry that guarantees the price until then. Other APIs usually call the locked kind a quote, and it is the one you store, because a transaction can point to it later. A platform fee is a third thing to model. Keep it as its own record, so one fee can apply to many payments.

On Lumx, a floating rate is a real-time reference with no price lock and no fee. A locked rate is requested with POST /exchange-rates and the type LOCKED, and it comes back with an id and an expiresAt, plus the base and final exchange rates and the final target amount. The lock lasts 30 seconds, one minute or five minutes. The 30-second lock has no fee, and the longer ones include a fee that covers volatility risk. You pass the id as exchangeRateId on the transaction, and once the lock expires the id is no longer valid.

A partner fee has a name, rates for on-ramps and off-ramps, and the wallet address where collected fees are sent. Each is a rate in basis points or a flat dollar amount, or both. A partner fee applies only when the request carries its partnerFeeId; there is no default. With a locked rate you pass it when you request the rate, so the fee becomes part of the quote, and the final target amount is the all-in figure. How payout pricing works shows where the lines land.

What states does a transaction move through?

A transaction is the durable record of a money movement. It has a type, its currencies and amounts, a status, payment details and a receipt on completion. The types do not share a status path, so give each its own enum instead of one list for all.

The three transaction types on Lumx, their documented status path and where each ends.

Type

Status path

Ends in

Webhook family

On-ramp (fiat to stablecoin)

AWAITING_FUNDS → TRANSFERRING_FIAT → TRADING → TRANSFERRING_STABLECOIN → SUCCESS

SUCCESS, failed or expired

onramp.* (7 events)

Off-ramp (stablecoin to fiat)

TRANSFERRING_STABLECOIN → TRADING → TRANSFERRING_FIAT → SUCCESS

SUCCESS or failed

offramp.* (5 events)

Transfer (stablecoin, no conversion)

TRANSFERRING_STABLECOIN → SUCCESS

SUCCESS or failed

transfer.* (3 events)

On-ramps also have a payment window, so the model needs a place for the expiry. A locked on-ramp expires with its rate. A floating one expires after one day on Pix (Brazil's instant payment system, run by the Central Bank) and SPEI (Mexico's interbank transfer system, run by Banxico), and after seven days on other rails. A deposit that lands after expiry is not processed: it is refunded to the sender on the same rail.

On Lumx, every on-ramp and off-ramp carries a purpose, one of nine values such as TRADE_TRANSACTIONS or PROFESSIONAL_SERVICES, which banking partners use for AML screening. A purpose that does not match the real use of the funds can hold the transaction. The response repeats your request and adds the state and a metadata object. On success it adds a receipt with the base and final exchange rate, the Lumx and partner fee lines, the final target amount and the transactionHash. A failed one carries an error message instead.

What should you keep in your own database?

Keep the provider's ID next to every record it maps to. The API returns amounts as decimal strings such as "10000.00", so store them as decimals with the currency beside them, never as floating-point numbers.

What to store from the API, by the record in your own system.

Your record

Store from the API

Why

User or business

customerId, verification status, metadata

The status decides what the user can do

Payee

destination id, holder relationship, verification status

Reuse the payee without resending bank details

Value held

wallet blockchain and address, account id and currency

A wallet and an account are different places

Payment

transaction id, type, status, your idempotency key, exchangeRateId, purpose, amounts

The row that finance and support both land on

Money trail

final exchange rate, fee lines, transactionHash

Proves the price paid and that the stablecoin leg happened

Webhook log

webhook-id, eventType, time received and processed

Deduplication and audit

I would draw the schema before the first call and build it around the transaction, not the customer. The customer table is the easy one. The transaction row is where the month-end questions land, so it should hold the rate you locked, the idempotency key you sent, the fee lines from the receipt and your own reference, all on one row. My test for a schema is whether support can go from a user complaint to one transaction row without joining four systems.

On Lumx, metadata is how your reference travels with the record. It is accepted on customers and on every transaction request, and it comes back on the transaction and in webhook payloads. Matching these rows against a ledger is covered in reconciling stablecoin transactions.

How webhooks and idempotency keep your copy honest

Statuses change after your call returns, so a webhook is how your copy of the model learns about it. Treat delivery as at least once. Retries mean a late copy of an older event can arrive after a newer one, so move a record forward through its states, never back, and deduplicate on the message ID. Every write that moves money also needs an idempotency key: a value you generate once and store, then resend unchanged on a retry.

On Lumx, every webhook has an eventId, an eventType and a data object, with webhook-id, webhook-timestamp and webhook-signature as headers. The signature is an HMAC-SHA256 over the ID, the timestamp and the raw body. There are 34 event types: 7 for on-ramps, 5 for off-ramps, 3 for transfers, 5 for customers, 4 for limit requests, 7 for accounts and 3 for destinations. If your endpoint does not return a 2xx, Lumx retries up to eight attempts, and the docs say to deduplicate on the webhook-id header.

The Idempotency-Key header takes a UUID v4 on any POST, PUT or PATCH. The same key with the same body returns the cached response, and the same key with a different body returns a 409 Conflict. Failed responses are cached too, and keys expire after 24 hours.

When mirroring the whole model is the wrong move

Copying every object and every status into your own database is the wrong move for a narrow integration. A payout-only product needs the customer, the destination and the transaction, and it can read a wallet balance when needed instead of mirroring it. Store the provider's status as text and map it to the three states your screens show: pending, done or failed. When the provider adds a status, your screens keep working. On Lumx, a payout-only build keeps customerId, destinationId and the transaction, and the rest of the model stays one read away.

What this model looks like when a platform pays a supplier abroad

Lumx is stablecoin payments infrastructure for businesses that move money between Latin America and the rest of the world: one API to collect, hold, convert, and pay out in BRL, MXN, COP, USD, EUR, and GBP or in USDC and USDT, over local rails such as PIX, SPEI, PSE, Bre-B, ACH, FEDWIRE, SEPA, and Faster Payments, with SWIFT and on-behalf-of payments and collections (POBO and COBO) in USD, EUR, and GBP, plus named virtual accounts, custodial wallets, and KYB/KYC built in.

The docs use one example for destinations: a Brazilian company paying a supplier in the United States. The company is created as a business customer, its owners go in as associated parties, and verification runs until the customer.approved event arrives. The wallets array then appears and the BRL account moves from REQUESTED to ACTIVE.

The supplier becomes a destination under that customer, with SUPPLIER as the holder relationship and the supplier's US bank details. To fund the wallet, it requests a 30-second locked rate for BRL to USDC and creates an on-ramp with the exchangeRateId. The customer pays through Pix, and the onramp events walk the status path until onramp.success. The off-ramp from the wallet to the destination then runs on a US rail, and offramp.success carries the receipt.

Rails and currencies by country are on the coverage page, and the product view of the same surface is global payments.

Verified on November 1, 2026. Operational context, not legal, tax, or investment advice.

Cover photo: Michal Chruscielski on Unsplash.

  • What is a stablecoin API data model?

    A stablecoin API data model is the set of objects the API uses to describe a payment and how they connect: the customers, the wallets and accounts that hold value, the destinations that receive it, the rates that price it and the transactions that move it, plus the webhook events that report changes. On Lumx, the objects are the customer, the wallet inside it, the account, the destination, the exchange rate, the transaction and the partner fee, each with a UUID and, except the wallet, its own endpoint.

  • How do a customer, its wallets and its accounts relate in a stablecoin API?

    The customer is the legal owner, and wallets and accounts are the two places its value can sit: stablecoin in a wallet, fiat in an account. Each has its own lifecycle. On Lumx, creating a customer provisions a wallet on every supported blockchain and an account whose status runs from AWAITING_ONBOARDING to ACTIVE, and both are usable once the customer is APPROVED.

  • How long does a locked exchange rate last in a stablecoin API?

    A locked rate lasts a fixed window set when you request it, and after it expires the ID is invalid and you request a new one. On Lumx, the window is 30 seconds, one minute or five minutes. The 30-second lock has no fee, the longer locks include a fee for volatility risk, and you pass the returned id as exchangeRateId on the transaction.

  • Which IDs should I store for each stablecoin payment?

    Store the customer ID, the transaction ID, the idempotency key you sent, the ID of any locked rate, the destination ID for payouts, and the webhook message ID you used to deduplicate. Keep amounts as decimals. On Lumx, the transaction response also returns your metadata object, the final exchange rate, the fee lines and, on success, the transaction hash.

  • Should I track a stablecoin payment with webhooks or by polling?

    Use webhooks as the main signal and a read of the transaction as the safety net, because delivery is at least once and a status can change while your call is still returning. On Lumx, every status change emits a webhook with an eventId, and the transaction can also be read by ID from the transactions endpoint if a delivery is missed.

Stay up to date with what Lumx is developing.

Sign up to receive them by email.

Share on socials:

stablecoin-api-data-model

A

stablecoin-api-data-model

Stablecoin API data model: customers, accounts, rates, transactions

Copy link

Copied!

stablecoin-api-data-model

TALK TO OUR TEAM

Ready to transform your business with stablecoins?

Discover how our infrastructure can seamlessly integrate stablecoins into your financial operations quickly, securely, and efficiently.

Explainers

In this page

©2026. All rights reserved.

Lumx US OP LLC ("Lumx") is a financial technology and payments infrastructure company. Lumx US OP LLC is a Money Service Business (MSB) registered with the Financial Crimes Enforcement Network (FinCEN) (MSB #31000316459619). Lumx is not a state-licensed money transmitter and does not, in its own capacity, engage in the provision of regulated money transmission services. All such regulated activities are conducted exclusively through, and under the licenses of, duly authorized financial-institution partners.


Lumx is not a bank, financial institution, payment institution, or custodian of client funds. Certain services made available through the Platform may be provided by duly authorized and regulated third-party partners, in accordance with applicable laws and regulations.

Please refer to Lumx’s Terms of Use and Privacy Notice for further information regarding the conditions governing the use of the Platform and the processing of your personal data.