Connect payments to orders.
Create invoices from your application. Track settlement with the API and signed webhooks.
1. Create an invoice
The recipient and requested amount are immutable after creation. Store the returned invoice ID, management key, webhook signing secret and payment URL on your server. The management key is returned once.
curl -X POST https://pay.tomant.ir/api/v1/invoices \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: ORDER-1042' \
-d '{
"recipient": "0xYOUR_BNB_RECEIVING_WALLET",
"amount": "250000.0",
"title": "Design services",
"reference": "ORDER-1042",
"expiresInHours": 24,
"webhookUrl": "https://your-store.com/tomant/webhook"
}'An Idempotency-Key prevents a retry from creating another invoice for the same receiving wallet. Reusing it with different details returns HTTP 409. The replay response does not expose the original private management key.
2. Send the payment URL
Send only paymentUrl to the customer. Keep managementUrl and managementKey private. Each link represents one invoice; create a new invoice for each customer order.
Public status includes the receiving address, requested and settled amounts, transaction receipts, and status. Private payer notes, webhook settings and delivery logs require:
openAwaiting paymentpartialConfirmed TOMANT is below the requested amountpaidFull TOMANT amount confirmed with 12 BNB Chain confirmationspaid_lateFull amount settled after the invoice expired; review before fulfilmentexpired / partial_expiredNo new checkout; payments already sent continue to be reconciledcancelledNo new checkout; already-sent payments can still be recorded3. Verify payment notifications
Webhooks send invoice.partially_paid and invoice.paid. The payload has id, type, createdAt and data (the invoice). The signature is HMAC-SHA256 of timestamp + '.' + rawBody, signed with that invoice’s webhookSecret.
import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody must be the ORIGINAL request body, not reserialized JSON.
const timestamp = req.headers['x-tomant-timestamp'];
const supplied = req.headers['x-tomant-signature'] || '';
if (!/^\d+$/.test(timestamp || '') ||
Math.abs(Date.now()/1000 - Number(timestamp)) > 300) {
throw new Error('Expired webhook');
}
const expected = 'v1=' + createHmac('sha256', signingSecret)
.update(timestamp + '.' + rawBody).digest('hex');
const a = Buffer.from(expected), b = Buffer.from(supplied);
if (a.length !== b.length || !timingSafeEqual(a, b)) {
throw new Error('Invalid signature');
}
const event = JSON.parse(rawBody);
// Deduplicate event.id before changing an order.
// Fulfil only on invoice.paid after checking your stored invoice,
// recipient, reference, token, chainId, and full amount.
// Acknowledge durable processing with HTTP 2xx.Delivery is at least once. Deduplicate by event ID. Failed delivery is retried up to 10 times with increasing delays (maximum delay 6 hours); undelivered events can be retried from the private invoice dashboard. Notifications require a public HTTPS endpoint; redirects are not followed.
Fetch notification history with GET /api/v1/invoices/:id/events and your invoice management key. Change an endpoint with PATCH /api/v1/invoices/:id and {"webhookUrl":"https://…"}.
4. Understand settlement
Direct transfers created by this checkout include an invoice-specific reference in their transaction data. In this pilot, Rango converts assets into the buyer’s own BNB Chain wallet; the buyer then confirms an exact TOMANT payment to the invoice recipient. Conversion success alone never marks a bill paid. Confirmed TOMANT transfer logs on BNB Chain determine the settled amount, and the same transfer log cannot be used on two invoices. Server-proxied merchant routing can be used when Rango server access is available.
Amounts in human-readable fields are decimal strings. amountUnits, receivedUnits and remainingUnits use TOMANT’s 18 on-chain decimals. Invoice amounts support one decimal place. Fees are additional and shown before signing.