API Documentation.
Integrate the Risksless card and crypto gateway into any stack — PHP, Node, Python, Go, mobile, no-code. The whole card flow is two GET requests: mint a tracking address bound to your USDC payout wallet, then send the customer to the hosted checkout. When the customer pays, we GET your callback URL with the settlement details and the USDC lands in your wallet.
Everything on one screen.
Base URLs, authentication and every endpoint. Each row links to its full section below.
https://api.risksless.com for all API calls (returns JSON, sends CORS headers, so it can be called from a browser or a server) · https://checkout.risksless.com for the hosted checkout pages your customer is redirected to.
address — settlements go there and nowhere else. All endpoints are plain HTTP GET with query-string parameters and JSON responses. Per-merchant API keys may be introduced later; if that happens the current endpoints will keep working and this page will be updated first.
| ENDPOINT | PURPOSE | SECTION |
|---|---|---|
GET /control/wallet.php | Mint a card tracking address (address_in) bound to your payout wallet and callback URL. | 02 · Card gateway |
GET checkout.risksless.com/pay.php | Hosted multi-provider checkout page. Recommended redirect target. | 02 · Card gateway |
GET checkout.risksless.com/process-payment.php | Direct checkout on one named provider (provider=stripe etc.). | 02 · Card gateway |
GET /control/provider-status/ | Live list of card providers, their status and minimum amount. | 03 · Provider status |
GET /control/convert.php | Convert an amount in EUR, CAD, INR, GBP… to its USD value. | 04 · Currency conversion |
GET {your callback URL} | We call you on settlement with value_coin, txid_out and more. | 05 · Callbacks |
GET /crypto/{ticker}/wallet.php | Mint a one-time crypto receiving address that forwards to your wallet. | 06 · Crypto gateway |
GET /crypto/{ticker}/convert.php | Fiat amount → coin amount for a ticker. | 06 · Crypto gateway |
GET /crypto/{ticker}/qrcode.php | Base64 PNG QR code for a receiving address. | 06 · Crypto gateway |
GET /crypto/{ticker}/info.php | Ticker metadata, minimum and live USD price. | 06 · Crypto gateway |
Four steps, every time.
The Payment Link Generator, the PHP SDK and all 19 official plugins do exactly this. If your integration does the same four things, it works.
┌ YOUR SERVER ───────────────────────────────────────────────────────────────┐ │ 1. GET api.risksless.com/control/wallet.php?address={your wallet}&callback={url} │ → { "address_in": "…" } # a fresh tracking address for THIS order │ Persist address_in with the order. │ │ 2. Redirect the customer to │ checkout.risksless.com/pay.php?address={address_in}&amount=99.00¤cy=USD&email=… └────────────────────────────────────────────────────────────────────────────┘ ┌ CUSTOMER ──────────────────────────────────────────────────────────────────┐ │ 3. Pays with Visa / Mastercard / Amex / Apple Pay / Google Pay / SEPA / UPI… │ on the hosted page. The provider settles USDC to address_in, which │ forwards it on-chain to your wallet. └────────────────────────────────────────────────────────────────────────────┘ ┌ RISKSLESS → YOUR SERVER ───────────────────────────────────────────────────┐ │ 4. GET {your callback url}&address_in=…&value_coin=96.53&coin=usdc&txid_out=0x… │ Verify, mark the order paid, reply 200 "OK". └────────────────────────────────────────────────────────────────────────────┘
- One
address_inper order. Every call towallet.phpmints a new tracking address. Call it once when the order is created, store the result, and never reuse anaddress_infor a second order — the callback is how you know which order was paid. - Steps 1 and 4 are server-side; step 2 is a browser redirect. Step 1 also works from the browser (the API sends CORS headers — the Payment Link Generator does it that way), but the callback must hit a public HTTPS URL you control.
- The callback is the source of truth. The customer returning to your site is not proof of payment. Only mark an order paid from the callback (or from the on-chain transfer to your wallet).
- You receive slightly less than
amount. Provider fees are deducted before settlement, sovalue_coinin the callback is normally a little below the charged amount. Compare with a tolerance — see Callbacks.
Build a checkout URL.
Card checkouts are a two-step flow. First generate a tracking address bound to your payout wallet, then send the customer to the hosted checkout URL.
Step 1 — Generate a tracking address
GEThttps://api.risksless.com/control/wallet.php ?address=0xYourPolygonWalletAddress &callback=https%3A%2F%2Fyoursite.com%2Fwebhook%3Forder%3D42%26token%3Dk3Jd9s
| PARAMETER | REQUIRED | DESCRIPTION |
|---|---|---|
address | Yes | Your merchant USDC wallet on Polygon: 0x followed by 40 hex characters. Must be a self-custodial wallet (MetaMask, Trust Wallet, Rabby, Ledger…). Do not use an exchange deposit address (exchanges freeze gateway payouts) and do not paste the USDC token contract address. All settlements for this address_in are forwarded here. |
callback | Yes | Absolute, publicly reachable HTTPS URL, URL-encoded exactly once. We GET it when the payment settles. Put your order ID and an unguessable per-order token in its query string — everything you include is echoed back on the callback so you can correlate and authenticate it. |
Response
{
"address_in": "0x8f2a91c84e3d…", # tracking address — use this as `address` in step 2
"polygon_address_in": "0x8f2a91c84e3d…", # plain Polygon form of the tracking address (for receipts / explorer lookups)
"callback_url": "https://yoursite.com/webhook?order=42&token=k3Jd9s",
"ipn_token": "…" # reserved; safe to ignore
}
| FIELD | DESCRIPTION |
|---|---|
address_in | The value you pass to the checkout page. Treat it as an opaque, already URL-safe string. On white-label deployments it is an encrypted token rather than a bare 0x address and may contain %2F / %3D. Store it verbatim. |
polygon_address_in | The underlying Polygon tracking address. Useful as a human-readable payment reference and for looking the order up on Polygonscan. On non-white-label deployments it is identical to address_in. |
callback_url | Echo of the decoded callback URL. Check it matches — if it is truncated, you did not URL-encode the callback parameter. |
ipn_token | Reserved token; safe to ignore. |
address_in, the request was rejected. The two causes we see: address is not a valid 0x… Polygon address, or callback is missing / not an absolute URL. Fix and retry — do not redirect the customer without an address_in.
Step 2 — Redirect the customer to checkout
Two checkout pages exist on checkout.risksless.com. /pay.php is the hosted multi-provider page: it geo-detects the customer and lets them pick the best provider for their country. /process-payment.php skips the picker and opens one named provider directly. Use /pay.php unless you have a reason not to.
Option A — Hosted checkout (recommended)
GEThttps://checkout.risksless.com/pay.php ?address=0x8f2a91c84e3d… # address_in from step 1, embedded RAW (see warning below) &amount=99.00 ¤cy=USD &email=customer%40example.com &logo=https%3A%2F%2Fyoursite.com%2Flogo.png # optional branding, see table
Option B — Direct to one provider
GEThttps://checkout.risksless.com/process-payment.php ?address=0x8f2a91c84e3d… &amount=99.00 &provider=stripe ¤cy=USD &email=customer%40example.com
| PARAMETER | REQUIRED | DESCRIPTION |
|---|---|---|
address | Yes | The address_in from step 1 — not your main wallet. Concatenate it into the URL as-is; do not URL-encode it again. |
amount | Yes | Amount to charge in currency, as a decimal with up to two places (99.00). No thousands separators, no currency symbol. Must meet the provider minimum (see Provider status). |
currency | Yes | Upper-case ISO 4217 code. USD, EUR, CAD and INR are exposed in the Payment Link Generator; the official plugins pass the store currency through and any currency that /control/convert.php can convert is accepted. Several providers are currency-locked — see the table below. |
provider | /process-payment.php: Yes/pay.php: No | Provider id as returned by /control/provider-status/. On /pay.php it may be omitted or set to hosted. |
email | No | Customer email, URL-encoded. Pre-fills the checkout and is used for the receipt. If omitted the checkout page asks for it. |
logo | No | /pay.php only. URL-encoded absolute URL of your logo, shown at the top of the hosted page. |
background | No | /pay.php only. Hex colour for the page background (URL-encode the #, e.g. %23111111). |
theme | No | /pay.php only. Hex colour for the page accent colour. |
button | No | /pay.php only. Hex colour for the pay button. |
address_in. It comes back from wallet.php already URL-safe. On white-label deployments it is an encrypted token containing %2F and %3D; passing it through encodeURIComponent, URLSearchParams, urlencode(), http_build_query() or urllib.parse.urlencode() turns those into %252F / %253D and the checkout silently fails the moment the customer picks a provider. Build the URL by string concatenation and encode only the other parameters. This single mistake is the most common reason an integration "loads the page but never completes".
Provider ids, currency locks and minimums
/control/provider-status/ is authoritative for what is active right now and for the minimum amount. The table below lists every id the platform knows, plus the locks and minimums that the official plugins enforce before redirecting, so you can validate on your side and show a helpful error instead of a failed checkout.
| PROVIDER ID | CURRENCY | MINIMUM | NOTES |
|---|---|---|---|
hosted | Any supported | 1 (client-side) | Recommended. Multi-provider page with geo-routing. Use /pay.php. Not listed in provider-status; it always exists. |
stripe | USD only | 2 USD | Cards, Apple Pay, Google Pay. |
revolut | Any supported | 6 EUR | Good for UK / EU customers. |
paypal | USD only | 5 USD | |
rampnetwork | USD only | 4 USD | |
robinhood | USD only | 5 USD | US customers. |
bitnovo | USD only | 10 USD | |
binance | Any supported | 15 USD | |
transfi | USD only | 70 USD | APAC coverage. |
wert | USD only | see provider-status | Higher approval rates for crypto-native merchants. |
upi | INR only | 1600 INR | India — UPI rails. |
interac | CAD only | 100 CAD | Canada — Interac e-Transfer. |
ideal | EUR only | 46 EUR | Netherlands — iDEAL. |
klarna | SEK | 125 SEK | Klarna. |
transak, moonpay, banxa, utorg, sardine, topper, simplex, mercuryo, alchemypay | Any supported | see provider-status | On-ramp providers. Availability varies by customer country and by day — alchemypay, for example, is currently reported as unstable, so always check status. |
customprovider | — | — | Placeholder for a provider tag assigned to your account by Risksless. Only use the exact tag you were given. |
Each minimum is in the provider's own currency, exactly as minimum_amount / minimum_currency report it. Where this table shows a value the official plugins enforce, the live value from provider-status still wins if it differs. When a provider is missing from provider-status or has a status other than active, do not route to it — fall back to hosted, which is a client-side choice and never appears in that response.
Code examples
JavaScript (browser or Node 18+)
// 1. Mint the tracking address (works client-side thanks to CORS, but do it // server-side if you need to trust the amount). const wallet = '0xYourPolygonWallet'; const callback = encodeURIComponent('https://yoursite.com/webhook?order=42&token=k3Jd9s'); const res = await fetch( `https://api.risksless.com/control/wallet.php?address=${wallet}&callback=${callback}` ); const { address_in } = await res.json(); if (!address_in) throw new Error('wallet.php returned no address_in'); // persist (orderId, address_in) here // 2. Redirect — address_in RAW, everything else encoded. const amount = (99).toFixed(2); // "99.00" const email = encodeURIComponent('customer@example.com'); window.location.href = `https://checkout.risksless.com/pay.php?address=${address_in}` + `&amount=${amount}¤cy=USD&email=${email}`;
Python (requests)
import requests from urllib.parse import quote WALLET = "0xYourPolygonWallet" callback = "https://yoursite.com/webhook?order=42&token=k3Jd9s" r = requests.get( "https://api.risksless.com/control/wallet.php", params={"address": WALLET, "callback": callback}, # requests encodes these once — correct timeout=15, ) address_in = r.json()["address_in"] # Concatenate: address_in raw, email encoded. checkout_url = ( "https://checkout.risksless.com/pay.php" f"?address={address_in}&amount={99:.2f}¤cy=USD" f"&email={quote('customer@example.com')}" ) # redirect the customer to checkout_url
cURL
# --data-urlencode encodes the callback exactly once curl -G "https://api.risksless.com/control/wallet.php" \ --data-urlencode "address=0xYourPolygonWallet" \ --data-urlencode "callback=https://yoursite.com/webhook?order=42&token=k3Jd9s" # → {"address_in":"0x8f2a…","polygon_address_in":"0x8f2a…","callback_url":"https://yoursite.com/webhook?order=42&token=k3Jd9s","ipn_token":"…"} # Then send the customer to: # https://checkout.risksless.com/pay.php?address=0x8f2a…&amount=99.00¤cy=USD&email=customer%40example.com
PHP developers: use the single-file PHP SDK, which wraps both steps and the callback verification.
List active card providers.
Returns every routing provider, its status, display name and minimum amount. Query it when you render a provider picker, or once at checkout to validate the amount before redirecting.
GEThttps://api.risksless.com/control/provider-status/ # Response { "providers": [ { "id": "stripe", "provider_name": "Stripe", "status": "active", "minimum_amount": 2, "minimum_currency": "USD" }, { "id": "paypal", "provider_name": "PayPal", "status": "active", "minimum_amount": 5, "minimum_currency": "USD" }, { "id": "revolut", "provider_name": "Revolut", "status": "active", "minimum_amount": 6, "minimum_currency": "EUR" }, { "id": "upi", "provider_name": "UPI", "status": "active", "minimum_amount": 1600, "minimum_currency": "INR" }, { "id": "alchemypay", "provider_name": "Alchemy Pay", "status": "unstable", "minimum_amount": …, "minimum_currency": "USD" }, # not "active" → do not route to it … ] }
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
id | string | The value to pass as provider on the checkout URL. |
provider_name | string | Human-readable label for your UI. |
status | string | active means routable. Skip any other value (for example unstable). |
minimum_amount | number | Minimum charge for this provider, in the provider's own currency (minimum_currency), not in USD. Treat a missing value as 1. |
minimum_currency | string | ISO code the minimum is expressed in: USD, EUR, SEK, INR or CAD. Live examples: revolut 6 EUR, ideal 46 EUR, klarna 125 SEK, upi 1600 INR, interac 100 CAD. Convert with /control/convert.php if you need to compare against a USD order. |
The Payment Link Generator uses this exact response to build its dropdown: it keeps only status === "active", shows each provider_name with its minimum_amount, and refuses to build a link below the minimum. When you display the minimum, print it with minimum_currency rather than assuming dollars. Note that hosted is not an entry in this response — it is a client-side option that always exists (it is the multi-provider /pay.php page), which is why the generator can fall back to it when this request fails. Do the same.
Non-USD orders to USD.
Settlement is always USDC, so an order in EUR, CAD, INR or GBP has to be compared against its USD value when the callback arrives. This endpoint gives you that value. Every official plugin calls it for non-USD stores.
GEThttps://api.risksless.com/control/convert.php ?from=eur # source currency, lower-case ISO code &value=99.00 # amount in that currency # Response (99.00 EUR) { "status": "success", "value_coin": "112.42", "exchange_rate": "1.13554" }
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
status | string | success when the conversion was performed. Treat anything else as a failure. |
value_coin | string | The USD value of value at the current rate, as a decimal string. Parse it as a float. |
exchange_rate | string | USD per one unit of from (value_coin = value × exchange_rate). Store it if you want to reproduce the conversion later. |
- When to call it. At order creation (store the USD value as
expected_usdalongsideaddress_in), or at callback time. Calling it at order creation is better — the rate you charged is the rate you verify against. - USD orders don't need it. If
currencyisUSD,expected_usdis just the amount. - If
value_coinis missing orstatusis notsuccess, the currency is not supported for conversion. Use USD, or one of EUR / CAD / INR, for that order. - Use the exact parameter names
fromandvalue. A request with a wrong or missing parameter name is answered with a302redirect to the site's error page rather than a JSON error — see Troubleshooting.
Get notified on settlement.
The callback URL you passed to /control/wallet.php (or to any /crypto/{ticker}/wallet.php) is requested by HTTP GET when the matching address_in receives funds and the required confirmations clear. This is the authoritative signal that a payment settled — tracking is event-driven, not poll-based.
Payload
# Your own query parameters come first, exactly as you passed them: GET https://yoursite.com/webhook?order=42&token=k3Jd9s &address_in=0x8f2a91c84e3d… # the tracking address that was paid &value_coin=96.53 # amount forwarded, in units of `coin` (net of provider fees) &coin=usdc # what was delivered — see the coin table &txid_in=0x9af2c1… # provider → address_in &txid_out=0xbc77d2… # address_in → your wallet (the payout tx; show it on receipts) &confirmations=3
| PARAMETER | DESCRIPTION |
|---|---|
| (yours) | Every query parameter in the callback URL you registered, unchanged. Use them for the order ID and the auth token. |
address_in | Tracking address that received the payment. Matches what you stored from step 1 (for white-label tokens compare polygon_address_in). |
value_coin | Decimal amount forwarded to you, in units of coin. This is net of provider fees, so it is usually a few percent below the amount you charged. |
coin | Asset delivered. Normally usdc (Polygon). Can be another stablecoin or a native coin — see below. |
txid_in / txid_out | On-chain transaction hashes. txid_out is the transfer into your wallet; store it as the payment reference. |
confirmations | Block confirmations at the time of the callback. |
Which coin can arrive
Payouts default to USDC on Polygon, but depending on the provider a payout may be delivered as USDT, POL (Polygon native), ETH or BNB — the same self-custodial wallet address receives all of them. Your callback handler must handle coin:
coin VALUE | MEANING | HOW TO GET THE USD VALUE |
|---|---|---|
usdc | USDC on Polygon (default) | value_coin is already ≈ USD. |
| other stablecoins (USDT / USDC on Polygon or BEP-20) | Stablecoin payout | value_coin is already ≈ USD. |
polygon_pol | POL, Polygon native | value_coin × prices.USD, where prices.USD comes from GET /crypto/{ticker}/info.php and {ticker} is the coin value with _ replaced by / (polygon_pol → polygon/pol, bep20_bnb → bep20/bnb, eth → eth). |
eth | Ether, Ethereum mainnet | |
bep20_bnb | BNB, BNB Chain |
Verifying the amount
Because value_coin is net of fees, an exact comparison against the order total will reject every legitimate payment. All official plugins use a tolerance threshold: accept when value_usd ≥ threshold × expected_usd. The plugins default to 0.60 (accept ≥ 60%), which tolerates the highest provider fees while still catching a customer who edited the amount on the checkout page. Tighten it (0.90) if all your traffic goes through low-fee providers.
// Node / Express — full callback handler app.get('/webhook', async (req, res) => { const { order, token, address_in, value_coin, coin, txid_out } = req.query; const o = await db.orders.findOne({ id: order }); if (!o || o.callback_token !== token) return res.status(403).send('bad token'); if (o.address_in !== address_in) return res.status(400).send('wrong address_in'); if (o.status === 'paid') return res.send('OK'); // idempotent // value_coin → USD let valueUsd = parseFloat(value_coin); if (['polygon_pol', 'eth', 'bep20_bnb'].includes(coin)) { const info = await fetch(`https://api.risksless.com/crypto/${coin.replace('_', '/')}/info.php`).then(r => r.json()); valueUsd = valueUsd * parseFloat(info.prices.USD); } // o.expected_usd was stored at order creation (via /control/convert.php for non-USD) if (valueUsd < 0.6 * o.expected_usd) { await db.orders.update(o.id, { status: 'failed', note: `underpaid ${valueUsd} / ${o.expected_usd}` }); return res.status(200).send('underpaid'); // 200: we handled it, don't retry } await db.orders.update(o.id, { status: 'paid', txid: txid_out, paid_usd: valueUsd }); res.status(200).send('OK'); });
Securing the callback
The callback is an unauthenticated GET. Anyone who learns the URL can request it, so never mark an order paid on the strength of the URL alone. Do all of the following — it is what the official plugins do:
- Put an unguessable per-order token in the callback URL when you call
wallet.php(a random 32-char string, or an HMAC of the order ID keyed with a secret). Reject callbacks whose token does not match the stored one. The WHMCS module usessig=HMAC-SHA256(invoice_id); the WooCommerce plugin uses a WordPress nonce stored on the order. - Check
address_inequals the one you stored for that order in step 1 (comparepolygon_address_inon white-label deployments). - Check the amount with the tolerance rule above.
- Be idempotent. The same settlement can be delivered more than once (retries). If the order is already paid, return 200 and do nothing.
- Optionally verify on-chain.
txid_outis a public Polygon transaction to your wallet; you can confirm it on Polygonscan or via any RPC before releasing high-value goods.
Responding
- Return an HTTP 2xx within 10 seconds. Any body is fine — the plugins return
OK. A non-2xx or a timeout causes the callback to be retried, so do slow work (emails, fulfilment) after responding or in a queue. - Return 2xx even for a payment you reject as underpaid — you have handled it, and a retry will not change the outcome. Log it for manual review.
- The URL must be reachable from the public internet over HTTPS.
localhost, private IPs and URLs behind HTTP auth will never receive a callback. Use a tunnel (ngrok, Cloudflare Tunnel) during development.
Generate a receiving address.
Accept crypto directly. Mint a one-time address that forwards any incoming funds to your destination wallet and fires the same callback as the card flow. Every endpoint is namespaced by ticker (polygon/usdc, trc20/usdt, btc, sol/usdc…).
Mint a receiving address
GEThttps://api.risksless.com/crypto/polygon/usdc/wallet.php ?address=0xYourDestinationWallet # a wallet on the SAME network as the ticker &callback=https%3A%2F%2Fyoursite.com%2Fwebhook%3Forder%3D42%26token%3Dk3Jd9s # Response { "address_in": "0x8f2a91c84e3d…", # show this to the customer (and a QR — below) "callback_url": "https://yoursite.com/webhook?order=42&token=k3Jd9s" }
The customer sends the coin to address_in. When it confirms, the funds are forwarded to the destination wallet you passed as address and your callback is requested with the same payload as a card payment (value_coin is in units of the ticker's coin, coin names it).
Convert fiat to coin amount
GEThttps://api.risksless.com/crypto/polygon/usdc/convert.php ?from=USD &value=99.00 # Response — how much of the coin equals 99.00 USD right now { "value_coin": "99.00", "coin": "usdc" }
Use it to tell the customer exactly how much to send. Note the difference from /control/convert.php: that one returns a USD value, this one returns a coin amount.
Render a QR code for the receiving address
GEThttps://api.risksless.com/crypto/polygon/usdc/qrcode.php?address=0x8f2a91c84e3d… # Response — qr_code is a base64 PNG { "status": "success", "qr_code": "iVBORw0KGgoAAAANS…" } <!-- inline it directly --> <img src="data:image/png;base64,{qr_code}" alt="Pay">
Ticker info, minimum and live price
GEThttps://api.risksless.com/crypto/polygon/usdc/info.php # Response (abridged) { "coin": "USD Coin (Native)", # display name of the coin (the key is `coin`, not `name`) "minimum": 0.665, # number — smallest amount that will be forwarded "prices": { "USD": "1.00", … } # live price per 1 coin — used to value native-coin payouts }
Supported tickers
Tickers are path segments. The wallet address you pass must be valid on that network.
| NETWORK | TICKERS | MINIMUM | TYPICAL CONFIRMATION |
|---|---|---|---|
| Polygon | polygon/usdc · polygon/usdt · polygon/pol | $1 | < 10 seconds |
| BNB Chain (BEP20) | bep20/usdc · bep20/usdt · bep20/bnb | $1 | < 15 seconds |
| Tron (TRC20) | trc20/usdt | $1 | < 30 seconds |
| Solana | sol/usdc · sol/sol | $2 | < 5 seconds |
| Ethereum (ERC20) | eth · erc20/usdc · erc20/usdt · erc20/dai | $20 | 1–3 minutes (gas-dependent) |
| Bitcoin | btc | $10 | 10–30 minutes |
| Litecoin | ltc | $5 | 2–5 minutes |
| Dogecoin | doge | $3 | 1–3 minutes |
| Other L2s | base/usdc · arbitrum/usdc · optimism/usdc · avax-c/usdc · linea/usdc | see info.php | seconds |
Always read minimum from info.php at runtime rather than hard-coding it. Try the flow interactively with the crypto wallet generator, which calls these four endpoints in the browser.
Vanilla PHP in two calls.
For PHP merchants without WooCommerce / WHMCS / OpenCart / PrestaShop, the same flow drops into a single-file SDK. Zero dependencies, no composer. It implements everything on this page — including the raw address_in rule, the currency conversion and the tolerance check.
Download risksless-php-sdk.zip, drop Risksless.php into your project, and require_once it. Requires PHP 7.2+ and the curl extension. The zip also contains example.php, a working pay + callback endpoint in one file.
Server-side: create a payment
// Persist $payment['address_in'] alongside your order, then redirect. require_once __DIR__ . '/Risksless.php'; $r = new Risksless('0xYourPolygonWallet'); $payment = $r->createPayment([ 'order_id' => 'INV-1042', // appended to callback_url as ?order=INV-1042 'amount' => 99.00, 'currency' => 'USD', 'email' => 'customer@example.com', 'callback_url' => 'https://yoursite.com/risksless-cb.php?token=k3Jd9s', // 'provider' => 'stripe', // optional; default is the hosted picker ]); // $payment = [ 'redirect_url' => …, 'address_in' => …, 'polygon_address_in' => …, 'callback_url' => … ] header('Location: ' . $payment['redirect_url']); exit;
Callback endpoint: verify settlement
// risksless-cb.php — the URL you passed as callback_url above. require_once __DIR__ . '/Risksless.php'; // 1. Authenticate the request with the token you put in callback_url. if (!hash_equals('k3Jd9s', $_GET['token'] ?? '')) { http_response_code(403); exit; } // 2. Load the order by $_GET['order'] and its expected total, then verify. $r = new Risksless('0xYourPolygonWallet'); $result = $r->verifyCallback($_GET, [ 'expected_amount' => 99.00, 'expected_currency' => 'USD', // non-USD is converted via /control/convert.php for you ]); if ($result['ok']) { // Mark the order paid; $result['txid_out'] is the on-chain payout tx. echo 'OK'; } else { // $result['reason'] is 'underpaid' or 'missing_value_coin'; // $result['value_coin'] and $result['expected_usd'] tell you by how much. echo $result['reason']; }
Default settlement threshold is 60% — pass a second argument to the constructor (new Risksless($wallet, 0.95)) to require ≥ 95% of expected before accepting. $r->listProviders() returns the /control/provider-status/ list if you want to render your own picker. Supported provider values: hosted (default), revolut, stripe, paypal, rampnetwork, transak, moonpay, banxa, utorg, transfi, sardine, topper, bitnovo, robinhood, upi, interac, simplex, binance, customprovider.
Ready-made modules for WooCommerce, WHMCS, OpenCart, PrestaShop and HOSTINPL are on the modules page.
The same API, no code.
The Payment Link Generator runs steps 1 and 2 in your browser and hands you the checkout URL. Use it to invoice by email, chat or SMS, or to test what a correct checkout URL looks like before you write any code.
- What it produces is exactly the
/pay.php(or/process-payment.php) URL described above, with a freshaddress_inminted for that link. One link = one payment; generate a new link for each invoice. - Its callback is a throw-away tracker URL, so no server is needed. You confirm payment by watching your wallet on the tracker / block explorer.
- Pre-fill from a URL. You can deep-link into the generator from your own pricing page or CRM:
https://risksless.com/payment-link/ ?wallet=0xYourPolygonWallet &amount=99 &email=customer%40example.com &auto=1 # optional: generate immediately and redirect the visitor straight to checkout
With auto=1 the page mints the tracking address and forwards the visitor to the hosted checkout without a click — a zero-backend "Buy now" button. Without it the form is pre-filled and the visitor presses Generate.
Before you message support.
These cover almost every integration ticket we receive. Check them in order.
| SYMPTOM | CAUSE | FIX |
|---|---|---|
| Checkout page opens but fails or hangs when the customer picks a provider | address_in was URL-encoded a second time (%252F in your URL). | Concatenate address_in raw. Encode only email, logo, etc. See the warning. |
| Payment completes but you never get a callback | Callback URL not public, not HTTPS, behind auth, or returned non-2xx. Or the callback parameter was not URL-encoded so its query string was cut off. | Check callback_url in the wallet.php response equals what you intended. Test your endpoint with curl from outside your network. Return 200 fast. |
| Callback arrives but you can't tell which order it is for | Nothing identifying was put in the callback URL. | Add ?order=…&token=… to the callback URL before encoding it, and/or index orders by address_in. |
| Legit payments rejected as underpaid | Exact comparison of value_coin against the amount. | Provider fees are deducted first. Use a tolerance (≥ 60% is the plugin default), and convert non-USD orders with /control/convert.php. |
value_coin is tiny (e.g. 0.031) for a $99 order | Payout arrived as a native coin (coin=eth, polygon_pol, bep20_bnb). | Multiply by prices.USD from /crypto/{ticker}/info.php before comparing. |
wallet.php returns no address_in | Invalid wallet (not 0x + 40 hex, or the USDC contract address), or missing callback. | Validate with /^0x[a-fA-F0-9]{40}$/ before calling. Use your own self-custodial wallet address. |
| Checkout says amount too low | Below the provider minimum. | Read minimum_amount and minimum_currency from /control/provider-status/ and validate before redirecting. The minimum is in the provider's currency, not USD. |
An API call answers with a 302 redirect to a Risksless error page instead of JSON | A parameter name is wrong or missing (for example amount instead of value on convert.php, or wallet instead of address). | Use the exact parameter names on this page. Your HTTP client may follow the redirect and hand you HTML — check the status code and content type before parsing. |
| Checkout rejects the currency | Provider is currency-locked (Stripe → USD, UPI → INR, Interac → CAD, iDEAL → EUR…). | Use provider=hosted, or match the lock. See the provider table. |
| Funds arrived on-chain but the exchange "froze" them | An exchange deposit address was used as the payout wallet. | Always use a self-custodial wallet (MetaMask, Trust Wallet, Rabby, Ledger). Withdraw to an exchange afterwards if you need to. |
| Browser console shows a CORS error | You called checkout.risksless.com with fetch. | Checkout pages are for navigation only. Only api.risksless.com is meant to be fetched. |
| Where is the sandbox / test mode? | There is no separate sandbox. | Test live with the smallest amount the provider allows (hosted: $1). It settles to your wallet like any other payment. |
address_in (or polygon_address_in) of the affected order and the full checkout URL you generated. With those two things we can see whether the payment was made, what was forwarded and what your callback returned.
Need a hand?
Work through the troubleshooting table first — it resolves most integration questions in minutes. If you are still blocked, include your address_in and checkout URL when you reach out.