Selling without a float
No prefunding. The quote carries its own payment instruction, so your customer pays at the point of sale.
The wallet is not a prerequisite. Quote with a payment rail instead of wallet and the
quote waits in awaiting_funds, carrying everything needed to pay it. This is the model
for a partner who takes money from a customer at the moment of sale and has nothing
sitting in a float.
Lightning: an invoice per purchase
curl https://api.tizon.mobile/v1/quotes \
-H "Authorization: Bearer $TIZON_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "offer_id": "off_jp_5gb_30d", "payment_source": "lightning" }'payment.lightning.bolt11 is the whole instruction — an invoice for exactly this quote's
price, minted for this quote alone, which expires and cannot be reused. Render it as a QR
code and let the customer pay.
{
"status": "awaiting_funds",
"price": { "amount": 1450, "currency": "USD" },
"payment": {
"rail": "lightning",
"amount": { "amount": 1450, "currency": "USD" },
"asset": null,
"asset_amount": null,
"destinations": [],
"lightning": {
"bolt11": "lnbc144u1p...",
"payment_hash": "9f2a...",
"amount_sats": 22656
}
},
"expires_at": "2026-10-02T12:15:00Z",
"order_id": null
}Paying it credits the wallet in USD and settles the quote in one move, so no funding step
is needed. The payer sends satoshis; you are credited the USD amount whatever the rate
does in between.
Stablecoins and bank transfer
With usdt, usdc or bank_transfer, payment.destinations holds the account's
standing destinations for that rail — the same addresses as
funding, not a per-quote address:
{
"payment": {
"rail": "usdt",
"amount": { "amount": 1450, "currency": "USD" },
"asset": "USDT",
"asset_amount": "14.50",
"destinations": [
{
"type": "crypto_address",
"asset": "USDT",
"network": "base",
"address": "0x5f1c9a3e7b424d189e650c7a2d4b8e31f0a9c6d2",
"memo": null,
"status": "active"
}
],
"lightning": null
}
}Read rail to know which fields matter: on Lightning, destinations is empty and the
invoice is everything; on the others, lightning is null and you send amount to one of
the destinations.
Because the destinations are standing, what arrives is credited to the wallet, not to the quote. There is no per-quote address to match a payment against.
How waiting quotes get paid
Every payment credits the wallet, and waiting quotes are then paid from the balance. The matching rule is deliberate and worth knowing:
- The quote priced exactly at what arrived goes first.
- Then the oldest quote that the balance can cover.
Money already in the wallet counts, so a quote may come back fulfilled immediately
rather than awaiting_funds.
Waiting quotes are paid from the balance only — never from an overdraft. An overdraft
is for payment_source: wallet.
Expiry
An unpaid quote expires at expires_at and becomes expired. Money that arrives after
that stays in the wallet; it is not lost, and it will pay the next thing that fits.
You can stop one early:
curl -X POST https://api.tizon.mobile/v1/quotes/{quote_id}/cancel \
-H "Authorization: Bearer $TIZON_KEY"Only awaiting_funds ever changes state. fulfilled, expired and cancelled are
final.
What to listen for
| Event | Means |
|---|---|
quote.awaiting_funds | The quote is waiting; the payment instruction is live. |
wallet.credited | Money arrived. A quote may settle on the back of it. |
quote.fulfilled | Paid and handed to fulfillment. An order now exists. |
quote.expired | Nobody paid in time. |
order.fulfilled | The eSIM or top-up is ready — read deliverable. |
Take order.fulfilled as the signal to hand something to your customer, not
quote.fulfilled: a fulfilled quote means paid, while the order carries the delivery
outcome.
Testing the whole flow
# Quote with lightning, then pretend the customer paid it:
curl -X POST \
https://api.tizon.mobile/v1/test_helpers/lightning_invoices/{invoice_id}/pay \
-H "Authorization: Bearer $TIZON_KEY"