Accept payments

Checkout page

Every invoice has a payment page on our domain. Send the customer there, or embed it in your own checkout with an iframe. The deposit address is only ever rendered by us, so an XSS on your site cannot swap it.

What the customer sees

  1. Network and coin. USDT on TRON and BSC, USDC and USDT on Base, Arbitrum and Polygon, USDT and TON on TON — the ones enabled for your account (GET /v1/assets), each with a hint about network fees. On TON the page also shows the transfer comment the customer must include.
  2. Terms. A checkbox: your terms and ours. Without it no address is shown.
  3. Payment details. The exact amount to send with a copy button and a reminder that exchanges charge a withdrawal fee, the full address with a QR code, a timer and a note that the address is for this payment only.
  4. Live status. Waiting, then "found, n of N confirmations", then paid, partially paid (with the amount left to send to the same address) or expired.
  5. "I paid but it is not showing". The customer can submit a transaction hash; we import it from the chain and your support sees it.

The page is in English or Russian (locale of the invoice) and works on phones first.

Hosted page

Redirect to invoice.hosted_url (https://pay.axiomis.app/i/inv_…). It shows your store name, a link back to cancel_url, and after payment a "Back to store" button to success_url. Use it in emails, chat bots and as a fallback when an iframe is not possible.

Landing on success_url does not prove payment: anyone can open that URL. Check the invoice on your server or wait for the webhook.

Iframe

Embed invoice.embed_url (https://pay.axiomis.app/embed/i/inv_…). The embedded page has no header and reports its height so the frame never scrolls. Add ?compact=1 when you already show the order summary next to it: the page then hides the amount header.

Allow your domain

The page can be framed only by origins on your allow list, sent as Content-Security-Policy: frame-ancestors. Give us exact origins with scheme and port, for example https://yourapp.com and https://staging.yourapp.com. Wildcards are not supported. If the list is empty, the iframe stays blank: we fail closed.

<div id="pay"></div>
<script>
  const PAY_ORIGIN = 'https://pay.axiomis.app';
  const frame = document.createElement('iframe');
  frame.src = invoice.embed_url + '?compact=1'; // from your server
  frame.title = 'Payment';
  frame.allow = 'clipboard-write';
  frame.style.cssText = 'width:100%;border:0;min-height:560px';
  document.getElementById('pay').appendChild(frame);

  window.addEventListener('message', (e) => {
    if (e.origin !== PAY_ORIGIN || e.data?.source !== 'axiomispay') return;
    if (e.data.type === 'resize') frame.style.height = e.data.height + 'px';
    if (e.data.type === 'status') {
      // A UI hint only. Grant access from the webhook, not from here.
      if (['paid', 'paid_overpaid', 'paid_late'].includes(e.data.invoice.status)) showThankYou();
    }
  });
</script>

Messages from the iframe

The page posts messages to your window only, targeted at your allowed origins (never '*'). Always check event.origin.

typePayloadWhen
resize{ source: "axiomispay", type: "resize", height: 612 }The content height changed. Set the iframe height to it.
status{ source: "axiomispay", type: "status", invoice: { id, status, replaced_by } }The invoice status changed. A UI hint: show a thank-you screen, close a modal.

Network change inside the iframe

If the customer switches network before paying, the invoice is closed and a new one is created for the same order. The iframe navigates to the new invoice by itself; you get invoice.cancelled with replaced_by for the old one. Do not treat that as a failed order.

Your own UI

We recommend the hosted page or the iframe: they handle the terms, timers, partial payments, network change and "I paid" for you, and keep the address out of your page. A fully custom checkout through the public API is possible, but it is not documented during the pilot. Talk to us if you need it.

Checkout page · Axiomis Pay Docs