Get started
Accept USDT in four steps
Axiomis Pay is a non-custodial USDT checkout. You create an invoice with one API call, the customer pays on our page, the money lands on addresses derived from your public key, and a signed webhook tells you it is paid. We never hold your funds or your keys.
Sign up with an email and a password: test mode, a test key and simulated payments are available right away. Live keys come after a short business form and a review (usually within 24 hours).
How it works
- Your server creates an invoice in USD. The response has a
hosted_urland anembed_url. - The customer opens the page, picks a coin and network (for example USDT on TRON or USDC on Base) and accepts the terms. Only then do we assign a deposit address from your pool.
- The customer sends USDT from any wallet or exchange. The gateway reads the chain itself and waits for enough confirmations.
- You get
invoice.paidby webhook with the amount received. Grant access. Funds stay on your addresses until you sweep them to your cold wallet.
1. Get a test key
A key looks like axp_test_… (test mode) or axp_live_… (live). It is shown once: store it in your secrets manager, never in the browser or in git. A test key only works with test networks, so you can build the whole integration without real money. See Test mode.
Check the key:
curl https://pay.axiomis.app/api/v1/merchant -H "Authorization: Bearer $AXIOMISPAY_KEY"
# {"ok":true,"merchant":{"id":"…","slug":"acme","name":"Acme"},"mode":"test"}2. Create an invoice
Install the SDK (Node.js 18+, TypeScript types included) or call the HTTP API directly.
npm install @axiomis-pay/sdkimport { AxiomisPayClient } from '@axiomis-pay/sdk';
const pay = new AxiomisPayClient({
apiKey: process.env.AXIOMISPAY_KEY, // axp_test_... in test mode
baseUrl: 'https://pay.axiomis.app/api',
});
const invoice = await pay.createInvoice(
{
amount_usd_cents: 2900, // $29.00
external_order_id: order.id, // your order ID
customer_ref: user.id, // your customer ID
description: 'Pro plan, 1 month',
success_url: 'https://yourapp.com/thanks',
},
{ idempotencyKey: `order-${order.id}` },
);
// Send the customer to the hosted page, or embed invoice.embed_url in an iframe.
return redirect(invoice.hosted_url);Amounts are integers in US cents, from 100 ($1) to 10000000 ($100,000). 1 USDT is treated as 1 USD. The invoice is open for 60 minutes after the customer picks a network. Full field list: Create an invoice.
3. Show the checkout
Redirect the customer to hosted_url, or embed embed_url in an iframe on your checkout page. The iframe needs your domain on the allow list: tell us the exact origins, for example https://yourapp.com. Details: Checkout page.
<iframe
src="https://pay.axiomis.app/embed/i/inv_…?compact=1"
title="Payment"
style="width:100%;border:0;min-height:560px"
allow="clipboard-write"
></iframe>4. Handle the webhook
Give us your webhook URL (HTTPS) and you get a secret whsec_…. Every event is signed; verify the signature on the raw body, answer 2xx fast, and process each event.id once.
import express from 'express';
import { verifyWebhook, PAID_STATUSES } from '@axiomis-pay/sdk';
const app = express();
// Raw body: the signature covers the exact bytes we sent.
app.post('/webhooks/axiomispay', express.raw({ type: '*/*' }), async (req, res) => {
let event;
try {
event = verifyWebhook(req.body, req.headers['axiomispay-signature'], process.env.AXIOMISPAY_WEBHOOK_SECRET);
} catch {
return res.sendStatus(400);
}
const invoice = event.data.invoice;
if (PAID_STATUSES.includes(invoice.status)) {
// Idempotent: the same event.id may arrive more than once.
await grantAccessOnce(invoice.external_order_id, event.id);
}
res.sendStatus(200);
});Grant access only from a verified webhook or from GET /v1/invoices/:id on your server. Never trust a redirect to success_url or a postMessage from the iframe: both can be faked by the customer.
Treat paid, paid_overpaid and paid_late as paid (PAID_STATUSES in the SDK). Other statuses and what to do with them: Statuses and edge cases.
Go live
- We set up your live pool from your xpub (public key) and check the first addresses with you. Your seed phrase never leaves your device.
- Switch the key to
axp_live_…. Your webhook endpoint stays the same; live events havemode: "live". Livesuccess_urlandcancel_urlmust be HTTPS. - Run one small real payment end to end before you announce it.