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
- Request sandbox API keys (or use the public demo).
- Register a receiving wallet with
POST /api/v1/walletsand verify it (sandbox_autoin sandbox, signature or admin review in live). - Create a payment session from your backend. Show the customer
pay_addressand the exactamount_expected. - Register a webhook URL. Verify
X-CM-Signaturebefore fulfilling. - Simulate
confirmed/underpaid/expiredin 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_tokenSimulation 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
| Method | Path | Purpose |
|---|---|---|
| POST | /payments | Create session |
| GET | /payments | List payments |
| GET | /payments/:id | Payment status |
| POST | /wallets | Register public address |
| POST | /wallets/:id/verify | Activate wallet |
| GET | /wallets | List wallets |
| POST | /webhooks | Register endpoint + secret |
| GET | /webhooks/deliveries | Delivery log |
| POST | /webhooks/deliveries/:id/resend | Retry delivery |
| POST | /sandbox/payments/:id/simulate | Sandbox only |
Networks
| Network | Token | Confirmations |
|---|---|---|
| TRC20 | USDT (TRON) | 19 |
| BEP20 | USDT (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.