Developer docs

USDT Checkout by MakeMeACoin API

Non-custodial USDT checkout. Customers pay your verified wallet. MakeMeACoin detects the on-chain transfer, waits for confirmations, and POSTs a signed webhook so your backend can mark the order paid. We do not take custody, convert to fiat, or operate as a licensed payment institution.

Quickstart

  1. Request sandbox API keys (or use the public demo).
  2. Register a receiving wallet with POST /api/v1/wallets and verify it (sandbox_auto in sandbox, signature or admin review in live).
  3. Create a payment session from your backend. Show the customer pay_address and the exact amount_expected.
  4. Register a webhook URL. Verify X-CM-Signature before fulfilling.
  5. Simulate confirmed / underpaid / expired in sandbox, then enable live after a small real USDT test.

How matching works

Payments go to the merchant's registered address — not a unique per-order deposit address. Each session gets an exact amount_expected with a unique marker in the 4th–6th decimal places. The customer must send that exact amount. The watcher matches the incoming USDT transfer to the open session by address + amount. Ambiguous matches go to manual_review instead of auto-fulfilling.

Wrong network, wrong token, or a rounded amount will not confirm the order. Tell the customer to send the displayed amount on the selected network only.

Authentication

Authorization: Bearer cmk_test_...
# or
X-Api-Key: cmk_test_...

# Sandbox keys: cmk_test_
# Live keys:    cmk_live_  (rejected until live mode is enabled)

Base URL: https://makemeacoin.xyz/api/v1

Create payment

POST https://makemeacoin.xyz/api/v1/payments
Idempotency-Key: order-123-v1
Content-Type: application/json

{
  "order_id": "order-123",
  "amount": "29.99",
  "currency": "USDT",
  "network": "TRC20",
  "metadata": { "customer_email": "buyer@example.com" }
}

Response includes pay_address (your wallet), amount_expected (exact send amount), qr_data, status, and expires_at. Poll GET /api/v1/payments/:id or wait for the webhook.

Wallets

POST https://makemeacoin.xyz/api/v1/wallets
{ "network": "TRC20", "address": "T...", "label": "treasury" }

# Sandbox: activate immediately
POST https://makemeacoin.xyz/api/v1/wallets/{id}/verify
{ "method": "sandbox_auto" }

# Live: sign the challenge, or request admin_review
POST https://makemeacoin.xyz/api/v1/wallets/{id}/verify
{ "method": "signature", "signature": "0x..." }

Public addresses only. Private keys and seed phrases are never requested or stored.

Webhooks

POST https://makemeacoin.xyz/api/v1/webhooks
{
  "url": "https://your-backend.example/webhooks/usdt",
  "events": [
    "payment.pending",
    "payment.confirming",
    "payment.confirmed",
    "payment.underpaid",
    "payment.overpaid",
    "payment.expired",
    "payment.failed",
    "payment.manual_review"
  ]
}
# Response includes secret (whsec_...) once. Store it.

Header: X-CM-Signature: t=<unix-seconds>,v1=<hmac-sha256-hex>. HMAC is computed over {timestamp}.{rawBody}. Reject timestamps older than 5 minutes. Fulfill only on payment.confirmed.

Webhook verification (Node.js)

import crypto from 'crypto';

function verifyWebhook(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map(p => p.trim().split('='))
  );
  const ts = parts.t;
  const v1 = parts.v1;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(ts + '.' + rawBody)
    .digest('hex');
  const age = Math.abs(Date.now() / 1000 - Number(ts));
  if (age > 300) return false;
  return crypto.timingSafeEqual(Buffer.from(v1, 'utf8'), Buffer.from(expected, 'utf8'));
}

Supabase Edge Function example

Verify the signature, then update the order row. Do not log the webhook secret.

import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

async function verifyWebhook(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map(p => p.trim().split('=')));
  const key = await crypto.subtle.importKey(
    'raw', new TextEncoder().encode(secret),
    { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
  );
  const sig = await crypto.subtle.sign(
    'HMAC', key, new TextEncoder().encode(parts.t + '.' + rawBody)
  );
  const expected = Array.from(new Uint8Array(sig))
    .map(b => b.toString(16).padStart(2, '0')).join('');
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  return age <= 300 && expected === parts.v1;
}

Deno.serve(async (req) => {
  const rawBody = await req.text();
  const signature = req.headers.get('x-cm-signature') || '';
  const secret = Deno.env.get('CM_WEBHOOK_SECRET');
  if (!secret || !(await verifyWebhook(rawBody, signature, secret))) {
    return new Response('invalid signature', { status: 401 });
  }

  const event = JSON.parse(rawBody);
  if (event.type !== 'payment.confirmed') {
    return new Response('ok', { status: 200 });
  }

  const payment = event.data.payment;
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!,
  );
  await supabase
    .from('orders')
    .update({ status: 'paid', tx_hash: payment.transactions?.[0]?.tx_hash ?? null })
    .eq('id', payment.order_id);

  return new Response('ok', { status: 200 });
});

Sandbox simulate

POST https://makemeacoin.xyz/api/v1/sandbox/payments/{id}/simulate
{ "scenario": "confirmed" }

# scenarios: pending, confirming, confirmed, underpaid, overpaid,
#            expired, failed, wrong_token

Simulation uses the same matcher as live watchers. If a webhook is registered, it is dispatched immediately. Live keys receive 403.

Try the live sandbox demo →

Payment statuses

  • created / pending — awaiting on-chain transfer
  • confirming — transfer seen, waiting for confirmations
  • confirmed — paid in full, fulfill the order
  • underpaid / overpaid — amount mismatch
  • expired — session timed out
  • failed / manual_review — do not auto-fulfill

Endpoint map

MethodPathPurpose
POST/paymentsCreate session
GET/paymentsList payments
GET/payments/:idPayment status
POST/walletsRegister public address
POST/wallets/:id/verifyActivate wallet
GET/walletsList wallets
POST/webhooksRegister endpoint + secret
GET/webhooks/deliveriesDelivery log
POST/webhooks/deliveries/:id/resendRetry delivery
POST/sandbox/payments/:id/simulateSandbox only

Networks

NetworkTokenConfirmations
TRC20USDT (TRON)19
BEP20USDT (BNB Smart Chain)12

Settlement

Funds land in the merchant wallet. MakeMeACoin does not deduct the platform fee from that transfer. The commercial fee (0.75% of confirmed volume, down to 0.50% at scale) is billed monthly. Refunds are sent by the merchant from the merchant wallet.

USDT Checkout by MakeMeACoin

Managed USDT checkout implementation for existing websites and backends.