Hosted checkout
Send your customer a link. They choose the currency and the network there, see a QR and an address, and watch it confirm. Nothing to build and nothing to host.
A non-custodial crypto payment gateway: one integration, a hosted checkout your customer can pay from on a phone, and money that goes from their transfer straight to your own wallet.
Test mode works immediately — no faucet, no testnet, nothing to fund.
Most gateways ask you to trust that they will forward your money. There is nothing to trust here, because there is no moment at which we have it.
We hold no balance at any point in this sequence.
A deposit address is computed from three things: the invoice, your payout wallet, and our fee. Nothing is deployed there — the contract that will live at that address does not exist yet, and when it is created it can do exactly one thing, which is pay the wallet that went into the hash. Change the wallet and you get a different address. We cannot point yours somewhere else without producing an address your customer never funded.
We compute the address and hand it back. No contract exists at it. No key controls it.
Straight to that address, from any wallet or exchange. We watch the chain for it.
A signed webhook fires. Then one transaction creates the contract and empties it.
Your wallet gets the payment less our fee, and we get the fee. Same transaction, no intermediate balance.
A hosted page, a link you send, an invoice inside your own app, or a plugin that needs no code at all. Every one of them settles the same way and reports through the same webhook, so adding a second is configuration rather than a second integration.
Sharing one account and one set of keys.
Send your customer a link. They choose the currency and the network there, see a QR and an address, and watch it confirm. Nothing to build and nothing to host.
Type an amount in the dashboard and get a link. For a seller with no site, no developer and one customer waiting — it works from a phone.
Open an invoice through the API and render it yourself. You get the amount, the address and the deadline; the payment flow stays inside your product.
Install, paste three values, done. An underpayment holds the order with the shortfall recorded rather than failing it — real money arrived.
And all four come with
Every payment gets a printable receipt carrying the transaction that proves it — the one line on it nobody has to take our word for.
Drive your handler end to end before you touch real money. The addresses are valid on no network, so nothing can be sent to one by accident.
Selling inside a bot? Stars go to your bot and stay there. We keep the record and take nothing, because there is nothing for us to take.
What arrived, what is short, what is waiting, and which currency stopped working and why. Written for the moment something looks wrong.
Your customer picks the currency and the network on the hosted page. Your payout wallet for that network is where the money lands, and nothing about your code changes from one network to the next.
A merchant can be paid on, through one API.
Which currencies and networks your account takes is set in the dashboard, where the list is current by construction. It is not printed here, where it would be a screenshot of last quarter.
You add a payout wallet for each network you take. Without one there is nowhere for the money to go, and the API says so rather than guessing.
A test address is valid on no network, so nothing can be sent to one by accident — and your webhook handler runs exactly as it would for real money.
Stars are not a network. They go to your bot and stay there; we keep the record beside your other payments and take nothing.
Our fee is a constructor argument of your deposit address. Settling on different terms would derive an address nobody funded, so the terms you were quoted are the terms that settle.
Inside the settlement transaction, not from a balance of yours.
No monthly charge, no setup, nothing to top up. Your rate is on your dashboard, current, beside the volume it is drawn from.
See your rateYour wallet gets the payment less our fee, and we get the fee — in the same transaction, with no intermediate balance anywhere.
Not on this page, where a figure printed today would be argued from next year.
Drive your own handler end to end without spending anything, then swap the key for a live one. Nothing else in your code changes.
On the one network where the payment lands straight in your own wallet, nothing of ours is in the path — so the fee is charged to a balance instead, and the balance and its limit are on your dashboard.
Two ways in. Open a checkout and send your customer a link — we handle the currency choice, the address, the QR and the waiting. Or open an invoice yourself when you already know which coin they are paying in and want to render the payment page inside your own app.
Lines of code for a working checkout, including the webhook.
Checkouts, invoices, Telegram Stars, payer endpoints, receipts, webhooks, and every error code, with the reasoning behind each one.
Open the API referenceInstall the plugin, paste your organisation id, an API key and a webhook secret, and you are taking crypto. An underpayment puts the order on hold with the shortfall recorded rather than failing it — real money arrived, and telling you otherwise would be wrong.
One request, one link. Amounts are strings of micro-dollars — never floats, because 19.99 has no exact binary representation and a shop at that price would be a cent out.
Send them url. File receiptUrl with the order — it answers
409 not_paid until the money lands, then becomes the receipt your
customer keeps.
# Ask for $19.99, and get a link to send your customer curl -X POST https://api.avexpay.net/v1/organizations/$ORG/checkouts \ -H "Authorization: Bearer $AVEX_KEY" \ -H "Content-Type: application/json" \ -d '{"amountFiatMicros":"19990000","reference":"order-1042"}'
# 201 Created { "id": "c0ffee00-…", "url": "https://avexpay.net/pay/c0ffee00-…", "receiptUrl": "https://avexpay.net/pay/c0ffee00-…/receipt", "mode": "live" }
Start with a key whose prefix is ak_test_. Everything behaves the same,
except the deposit addresses are not valid on any chain — nothing can
be sent to one by accident. Pay them from the API and your webhook handler runs
exactly as it would for real money.
A test key cannot open a live invoice however it asks, and a live key cannot opt into test mode. Every invoice and every webhook says which it is, so a key shipped to production by mistake is visible in your own logs.
# Drive your own handler end to end without spending anything curl -X POST https://api.avexpay.net/v1/organizations/$ORG/invoices/$ID/simulate-payment \ -H "Authorization: Bearer $AVEX_TEST_KEY"
Only a signed webhook should complete an order. The page your customer returns to is a URL anybody can visit, so it must never change an order's status.
Compare the whole string in constant time — == on a signature leaks how
much of a guess was right. Retries are signed afresh, so a replayed old delivery
fails the timestamp check rather than the signature one.
# avex-signature: t=1787048882,v1=9f2c41ab… # Sign "<t>.<raw body>" with your endpoint secret, compare in constant time, # and reject a timestamp more than 300 seconds old. signed = f"{t}.{raw_body}" expected = hmac.new(secret, signed.encode(), hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, v1): return 400
Every failure is { "error": "code", "message": "…" }. Branch on
error; show message to a person. The status says which of us
has to act.
| Code | What it means | Whose move |
|---|---|---|
| no_payout_address | Add a wallet for that chain. Without one there is nowhere for the money to go. | yours |
| asset_unlisted | We have stopped accepting that currency. Invoices already open still complete. | ours |
| price_unavailable | No rate we trust right now. Retryable — we would rather refuse than misprice you. | ours |
| fixed_rate_expired | The rate you set for your own token lapsed. Set a current one. | yours |
There is one 402, and it is narrow. On the networks where our commission
is taken out of the payment itself, nothing about your account with us can refuse you
an invoice. On the one network where the payment goes straight into your own wallet,
there is nothing of ours in the path to take a commission from — so it is charged to a
balance instead, and past a limit new invoices on that network wait until the balance
is settled. Every other network keeps working, and those invoices are what clear it.
The balance and the limit are on your dashboard, current, rather than in a page
somebody would have to remember to update.
Create an account and you have a test key immediately. Drive your own webhook handler end to end, then swap the key for a live one — nothing else in your code changes.
Already have one? Sign in to the dashboard.