Skip to content
VEXORAGATE

How to Accept USDT Payments on Your Website: A Practical Guide

A step-by-step guide to accepting USDT on your site — from creating a merchant and your first invoice to signed webhooks, testing and getting paid out — without running your own wallet infrastructure.

By VexoraGate Team7 min read
Cover image: How to Accept USDT Payments on Your Website: A Practical Guide

USDT is the most widely used stablecoin, and for many online businesses it has become a practical way to get paid by customers anywhere in the world. A USDT payment settles in minutes, does not depend on card networks, and cannot be charged back the way a card payment can. The hard part has never been the coin itself — it is everything around it: generating a fresh address for every order, watching several blockchains, matching incoming transfers to orders, handling customers who send too little or too late, and keeping the keys safe.

This guide walks through the whole process with VexoraGate, from an empty account to a live checkout. You can follow it without writing code (payment links), or use the API if you want the checkout fully built into your site.

What you need before you start

You need three things, and none of them takes long:

  1. A VexoraGate account. Sign up at vexoragate.com/register with an email address and a password, then confirm the email. There are no ID documents or selfies involved.
  2. Two-factor authentication (2FA). Turn it on under Security with an authenticator app. It protects your account and is required before any money can leave it.
  3. A merchant. Switch your dashboard from Personal to Business and create a merchant for your website. A new merchant starts as Pending moderation: our team takes a quick look at the website and what you sell. This is a review of the project, not of your identity. Once it is approved you can create invoices and API keys.

One account, two sides

Every VexoraGate account has a personal wallet and a business side. Customer payments land in the business balance of the merchant they were paid to, and you can move money between business and personal instantly.

Step 1: Pick how customers will pay

There are four ways to take a payment. You can mix them on the same merchant.

OptionBest forCode needed
Payment linkInvoices sent by email or chat, one-off sales, freelancersNone
Hosted payment page via APIOnline shops and SaaS checkoutsA few API calls
Plugin or SDKStores on common e-commerce platformsConfiguration only
Static walletTop-ups, Telegram bots, repeat customersA few API calls

Payment links are created in the dashboard: enter an amount (for example 49.90 USD), an order reference and an optional note, then copy the link. Your customer opens a clean payment page with the exact amount, a QR code, a countdown and a live status.

The API does the same thing programmatically: your server asks for an invoice, gets back a payment page URL, and redirects the customer there. When the payment arrives, we notify your server with a signed webhook.

Static wallets give each customer a permanent deposit address instead of a one-time invoice. They are covered in a later guide on this blog.

Step 2: Create your API keys

In Business → API, generate a payment key. It is shown once and stored encrypted on our side, so put it straight into your server's secret storage (an environment variable, not your code repository). You can optionally restrict the key to your server's IP addresses.

If you will also send payouts from your business balance, generate a separate payout key. Keeping the two apart means a leaked checkout key can never move money out.

Step 3: Create your first invoice

Every request is a POST with a JSON body and two headers: merchant (your merchant UUID) and sign, which is md5(base64(body) + API_KEY). Here is a minimal Node.js example with no dependencies:

import crypto from "node:crypto";

const BASE = "https://api.vexoragate.com";
const MERCHANT = process.env.VEXORA_MERCHANT_UUID;
const PAYMENT_KEY = process.env.VEXORA_PAYMENT_KEY;

const sign = (body, key) =>
  crypto.createHash("md5").update(Buffer.from(body).toString("base64") + key).digest("hex");

async function createInvoice(orderId, amountUsd) {
  const body = JSON.stringify({
    amount: amountUsd,
    currency: "USD",
    order_id: orderId,
    url_callback: "https://shop.example/vexora/webhook",
    url_success: "https://shop.example/thank-you",
  });
  const res = await fetch(BASE + "/v1/payment", {
    method: "POST",
    headers: { merchant: MERCHANT, sign: sign(body, PAYMENT_KEY), "Content-Type": "application/json" },
    body,
  });
  const json = await res.json();
  if (json.state !== 0) throw new Error(json.message ?? JSON.stringify(json.errors));
  return json.result; // result.url is the payment page
}

A few details are worth knowing from the start:

  • Currency. You can price invoices in USD, where 1 USD = 1 USDT = 1 USDC, or directly in a coin. If you price in USD and the customer pays in a non-stable coin, the rate is fixed when the invoice is created.
  • Network. If you leave network out, the customer chooses on the payment page among the networks you accept. If you set it (for example tron), the invoice is fixed to that network.
  • order_id is idempotent. Creating an invoice twice with the same order_id returns the same invoice instead of a duplicate, so a retry after a timeout is safe.
  • Lifetime. Invoices expire after one hour by default; you can set anything from 5 minutes to 12 hours with lifetime (in seconds).

Redirect the customer to result.url and you are done with the checkout side.

Step 4: Confirm payments with webhooks

Never mark an order as paid because the customer came back to your "thank you" page — that page can be opened by anyone. The source of truth is the webhook: on every status change we send a POST with the invoice data to your url_callback, signed with your payment key.

Your handler should do four things:

  1. Verify the sign field (the exact algorithm, with Node and PHP examples, is in the webhooks section of the docs).
  2. Look up the order by order_id.
  3. Act only on final, successful statuses: paid and paid_over.
  4. Be idempotent — the same webhook may arrive more than once, so delivering an order twice must be impossible.

Reply with HTTP 2xx. If your server is down, we retry up to 10 times over about 28 hours, and every delivery is logged under Business → Webhooks, where you can also resend.

Underpaid, overpaid and late payments

If a customer sends too little, the invoice moves to wrong_amount_waiting and they can top up until it expires. Paying more than requested gives paid_over. A payment that arrives after expiry is still credited and reported by webhook, and a payment in the wrong coin is held for review instead of being lost.

Step 5: Test before you go live

Two habits save a lot of trouble:

  • Send test webhooks. The /v1/test-webhook/payment endpoint sends a correctly signed webhook with any status you choose to any URL, so you can check your handler for paid, wrong_amount and cancel without moving money.
  • Make one small real payment. Create a low-value invoice, pay it from your own wallet on the network you expect most customers to use, and watch it go from check to paid in the dashboard and in your logs.

What it costs

Accepting payments has no setup fee and no monthly fee, and deposits are free. A processing fee is taken only from successful payments. The default rate and the per-network limits are always listed on the fees page, and your own rate is returned by /v1/payment/services.

You also decide who pays that fee. By default the merchant pays it out of the received amount; with the subtract setting (0–100) you can move part or all of it to the customer, who then sees a slightly higher amount on the payment page.

Getting your money out

Payments are credited to your merchant's business balance after the network confirms them. From there you can:

  • Transfer to your personal wallet instantly (2FA required).
  • Send a payout to any external address from the dashboard or with the /v1/payout API. Every payout is reviewed by a person on our team, sent, and then verified on the blockchain before it is marked paid.

Common mistakes to avoid

  • Trusting the redirect instead of the webhook. Always confirm with a signed webhook.
  • Signing a different body than you send. Sign the exact bytes of the request body; re-serialising the JSON after signing breaks the signature.
  • Ignoring paid_over and late payments. Treat paid_over as paid and decide in advance how you handle the extra amount.
  • Reusing one invoice for several orders. One order, one invoice — the unique address per invoice is what makes automatic matching reliable.
  • Keeping keys in the front end. API keys belong on your server only.

Your launch checklist

  • Account created, email confirmed, 2FA on
  • Merchant approved
  • Payment key stored as a server secret (and IP allowlist set, if possible)
  • Invoice creation working with a unique order_id per order
  • Webhook handler verifies sign, is idempotent and handles paid / paid_over
  • Test webhooks sent for success and failure statuses
  • One small real payment completed end to end

That is everything you need to take your first USDT payment. The full API reference is in the documentation, and if you get stuck our support team is on Telegram at @vexoracard_support. New guides appear on this blog every week — next up is a comparison of the networks you can accept USDT on, and what each one means for fees and speed.

Share:TelegramX

Accept crypto payments with VexoraGate

Create a free account, set up your merchant and get paid in USDT on six networks. No setup or monthly fees.