Accept payments
Invoice, hosted checkout, webhook.
Price the order in the currency you sell in. We return a checkout URL, your customer picks a chain and pays there, and you fulfil when the signed webhook arrives. They never make an account — with you or with us.
Northwind Supply
order_8812
49.90 USD
Pay with
- USDTTron
- USDCBase
- USDTBNB Chain
Your payer needs no account — with you or with us. You fulfil on the signed webhook, never on the redirect.
How it works.
Create the invoice
One POST with an amount, a pricing currency and your own reference. You get back a checkout URL, one payment address per chain, and a locked amount in every coin you accept.
They pay, at a rate that stays honest
Your customer is never asked to commit to a chain first — one address serves every EVM chain, so whatever arrives is attributable. Stablecoins are quoted once. A volatile coin is re-quoted every two minutes, and the figure it replaces is still honoured for five, so nobody who did as they were told is told they underpaid.
You get a signed webhook
HMAC-SHA256, retried for about 46 hours, replayable from the dashboard. The payment is credited to your balance per asset.
Your first call.
curl https://api.molnpay.com/v1/invoices \ -H "Authorization: Bearer $MOLNPAY_SECRET_KEY" \ -H "Idempotency-Key: order_8812" \ -d amount="49.90" \ -d currency="USD" \ -d reference_id="order_8812"
{
"object": "invoice",
"id": "9f3c1b2e-…",
"status": "open",
"price_amount": "4990",
"price_currency": "USD",
"checkout_url": "https://pay.molnpay.com/i/ck_9f3c…",
"expires_at": "2026-09-16T12:30:00.000Z",
"quotes": [
{ "chain": "bsc", "asset": "USDT",
"amount": "49.90", "amount_atomic": "49900000000000000000", "decimals": 18 }
]
}`price_amount` is minor units — 4990, not 49.90 — and `amount_atomic` is what your customer actually sends. The two human-readable figures beside them are derived for display and are never what a payment is checked against. `decimals` is 18 here because USDT on BSC is an 18-decimal token where everywhere else it is 6; the amount is computed with that number, so the amount is right.
Worth knowing before you build.
- Amounts are strings, everywhere
- Including here, where 49.90 would fit in a double. One rule with no exceptions — the moment the API has “amounts are strings except sometimes”, an integrator writes the exception into their deserializer and then meets a wei value.
- Under- and overpayment are states, not tickets
- A ±0.5% tolerance band per invoice, published rather than negotiated. Inside it, paid. Under by more than the band leaves the address live, shows your customer the remainder with its own QR, and extends the window so they do not lose it mid-transfer. Over by more than the band records the excess as its own figure — kept and credited to you, or offered back to your customer on the page, whichever you set.
- The checkout routes are the only unauthenticated ones
- They are authorised by the invoice's own opaque token rather than by a key, because the payer has no account with us and never will.
- Refunds are free, and the address is asked for
- We charge nothing to send one — nobody should pay twice for one sale. The network does charge, and that comes out of the refund rather than off your balance. Your customer confirms where it goes on the same page: prefilled with the address they paid from when that looks like their own wallet, and left blank when it looks like an exchange, because a refund into an exchange's deposit address credits nobody and cannot be undone.
The events you will listen for.
Only one of these means money has arrived, and it is invoice.paid. Fulfilling on anything earlier — or on a browser redirect — is the most common integration bug in this category.
- invoice.created
- Priced, addressed and quoted.
- invoice.pending
- Seen on chain, below finality. Not money.
- invoice.paid
- Final. This is the one that means money.
- invoice.underpaid
- Short by more than the band. Do not ship.
- invoice.overpaid
- Over by more than the band; the excess is its own figure.
- invoice.expired
- The window closed unpaid.
- invoice.canceled
- Cancelled while unpaid.
- invoice.refunded
- The excess went back, to an address the payer confirmed.
The rest of the integration.
One key, one dashboard, one webhook signature. These are capabilities, not separate products to buy.