# NoncePay > Accept BTC, USDT, USDC, ETH, SOL, LTC, DOGE, BNB and TRX on thirteen networks. NoncePay forwards every payment straight to your own wallet, minus 1.25%. No account, no API key. Base URL: https://api.noncepay.app. Every endpoint is a GET with query parameters and a path ending in "/". No API key: the callback URL identifies a payment address. # Build with AI NoncePay is small enough to integrate in one sitting, and an AI coding agent can do most of it for you. Give it the prompt below together with a description of your stack (framework, database, how you deploy), and it builds the client, the callback endpoint, the migrations and the tests. ## What the prompt covers - Creating payment addresses, QR codes and fiat conversions, with configuration instead of hardcoded wallets. - Receiving pending and confirmed callbacks in GET, form or JSON form. - Verifying the RSA-SHA256 `x-ca-signature`, including behind a reverse proxy. - Idempotency on `uuid` with a database unique constraint, partial payments and overpayments. - Every error message of the API and which ones to retry. - A 21-case test suite your agent writes alongside the code: signatures, flows, idempotency, money and client behaviour. ## Tips - Append your stack to the prompt, for example: _"Stack: Laravel 11, MySQL, deployed on Forge. Accept USDT on Tron and USDC on Base."_ - Ask the agent to run the test suite before it reports back, and to expose the callback URL with an HTTPS tunnel while developing: callbacks to `localhost` are refused. - The prompt is also served raw at [`/prompt.md`](/prompt.md), and the full documentation as text at [`/llms-full.txt`](/llms-full.txt), for agents that browse. # Callbacks ## When callbacks are sent | Moment | `pending` | Sent when | | ----------------- | --------- | ---------------------------------------------------- | | Payment seen | `1` | Only if you created the address with `pending=1` | | Payment forwarded | `0` | Always, once the amount has been sent to your wallet | Each payment to an address gets its own pair of callbacks. Addresses never expire. ## Transport - **GET** (default): every field is added to the query string of your callback URL. - **POST form** (`post=1`): fields in an `application/x-www-form-urlencoded` body. - **POST JSON** (`json=1`): fields in a JSON body. Your own query parameters always stay on the URL. ## Fields | Field | Description | | ---------------------- | ----------------------------------------------------------- | | `uuid` | Payment id, identical on its pending and confirmed callback | | `address_in` | The address that was paid | | `address_out` | Your wallet, or the split | | `txid_in` | Customer's transaction | | `txid_out` | Our forwarding transaction (confirmed only) | | `confirmations` | Confirmations at the time of the callback | | `value_coin` | Amount paid | | `value_forwarded_coin` | Amount sent to your wallet (confirmed only) | | `fee_coin` | Our fee (confirmed only) | | `coin` | Ticker in underscore form, such as `trc20_usdt` | | `price` | Coin price in USD | | `pending` | `1` or `0` | | `result` | `pending` or `sent` | | `value_coin_convert` | Fiat values, with `convert=1` | `value`, `value_forwarded` and `fee` are legacy integer fields: read the `_coin` strings instead. ## Retries Answer **HTTP 200** with the body **`*ok*`** within 30 seconds. Anything else is retried after 6, 12, 24 minutes and so on, doubling, until the payment is 3 days old. Every attempt is visible in [`/{ticker}/logs/`](/docs/api/payments/get-logs). ## Verifying the signature Every callback carries `x-ca-signature`: a base64 RSA-SHA256 (PKCS#1 v1.5) signature of the **full URL** for GET callbacks, or of the **raw body** for POST callbacks. Verify it with the key from [`/pubkey/`](/docs/api/utilities/get-public-key). ```js title="Node.js" import { createVerify } from 'node:crypto' export function verify(signedData, signature, pubkey) { return createVerify('sha256').update(signedData).verify(pubkey, signature, 'base64') } ``` ```python title="Python" import base64 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding def verify(signed_data: bytes, signature: str, pubkey_pem: str) -> bool: key = serialization.load_pem_public_key(pubkey_pem.encode()) try: key.verify(base64.b64decode(signature), signed_data, padding.PKCS1v15(), hashes.SHA256()) return True except Exception: return False ``` ```php title="PHP" r.json()) if (body.status !== 'success') throw new Error(body.error) return body // { address_in, minimum_transaction_coin, ... } } // const { address_in } = await createAddress({ ticker: 'trc20/usdt', orderId: 42, wallet: 'T…' }) ``` ## Receive and verify the callback ```js title="server.js" import { createVerify } from 'node:crypto' import express from 'express' const { pubkey } = await fetch('https://api.noncepay.app/pubkey/').then((r) => r.json()) const seen = new Set() // use your database in production const app = express() app.get('/paid', (req, res) => { const signed = `https://${req.headers.host}${req.originalUrl}` const signature = req.headers['x-ca-signature'] const valid = createVerify('sha256').update(signed).verify(pubkey, signature, 'base64') if (!valid) return res.status(401).end() const { uuid, pending, order, value_forwarded_coin, coin } = req.query if (pending === '0' && !seen.has(uuid)) { seen.add(uuid) console.log(`order ${order} paid: ${value_forwarded_coin} ${coin} sent to your wallet`) } res.send('*ok*') }) app.listen(3000) ``` Behind a proxy, rebuild the URL exactly as we called it (scheme, host and query string), or the signature will not match. For `post=1` or `json=1` callbacks, verify the raw request body instead of the URL. Next: [callback fields](/docs/callbacks) and the [API reference](/docs/api/payments/create-address). Runnable shops with this flow: [Express](https://github.com/Nonce-pay/noncepay-examples/tree/main/node/express) and [Next.js](https://github.com/Nonce-pay/noncepay-examples/tree/main/node/nextjs). # Accept crypto payments with PHP Plain PHP 8 with the `openssl` and `curl` extensions; no framework or SDK needed. ## Create a payment address ```php title="create_address.php" "https://shop.example/paid?order={$orderId}", 'address' => $wallet, 'pending' => 1, ]); $body = json_decode(file_get_contents("https://api.noncepay.app/{$ticker}/create/?{$query}"), true); if (($body['status'] ?? '') !== 'success') { throw new RuntimeException($body['error'] ?? 'Unknown error'); } return $body; // address_in, minimum_transaction_coin, ... } ``` ## Receive and verify the callback ```php title="paid.php" dict: response = requests.get( f"{API}/{ticker}/create/", params={ "callback": f"https://shop.example/paid?order={order_id}", "address": wallet, "pending": 1, }, timeout=15, ) body = response.json() if body.get("status") != "success": raise RuntimeError(body.get("error")) return body # address_in, minimum_transaction_coin, ... ``` ## Receive and verify the callback ```python title="app.py" import base64 import requests from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding from flask import Flask, request PUBKEY = serialization.load_pem_public_key( requests.get("https://api.noncepay.app/pubkey/", timeout=15).json()["pubkey"].encode() ) app = Flask(__name__) def is_signed_by_noncepay(data: bytes, signature: str) -> bool: try: PUBKEY.verify(base64.b64decode(signature), data, padding.PKCS1v15(), hashes.SHA256()) return True except Exception: return False @app.get("/paid") def paid(): if not is_signed_by_noncepay(request.url.encode(), request.headers.get("x-ca-signature", "")): return "", 401 if request.args.get("pending") == "0": order = request.args["order"] amount = request.args["value_forwarded_coin"] # mark the order paid, once per uuid return "*ok*" ``` `request.url` must be the URL exactly as we called it; behind a proxy, configure Flask's `ProxyFix`. For `post=1` or `json=1` callbacks, verify `request.get_data()` instead. Runnable shops with this flow: [Flask](https://github.com/Nonce-pay/noncepay-examples/tree/main/python/flask) and [FastAPI](https://github.com/Nonce-pay/noncepay-examples/tree/main/python/fastapi). # Accept crypto payments with WooCommerce The NoncePay plugin for WooCommerce adds a crypto payment method to your store. Customers pick a coin at checkout, see the exact amount, a QR code and a payment address, and the order updates by itself once the payment is confirmed. Every payment is forwarded to your own wallet, minus 1.25%. There is no account, no API key and no withdrawal step. ## Install 1. In WordPress, go to **Plugins > Add New**, search for **NoncePay**, then install and activate [NoncePay – Crypto Payment Gateway for WooCommerce](https://wordpress.org/plugins/noncepay-for-woocommerce/). 2. Go to **WooCommerce > Settings > Payments > NoncePay**, enable it and enter a receiving wallet for each coin you want to accept. Only coins with a wallet are offered at checkout. Requires WordPress 6.4+, WooCommerce 8.0+ and PHP 7.4+. The plugin is translated into 12 languages. Without access to the plugin directory, download `noncepay-for-woocommerce.zip` from the [latest release](https://github.com/Nonce-pay/noncepay-woocommerce/releases/latest) and upload it in **Plugins > Add New > Upload Plugin**. The source is on [GitHub](https://github.com/Nonce-pay/noncepay-woocommerce). ## What it does - **Coins:** USDT on Tron, BNB Smart Chain and Ethereum; USDC on Base, Polygon, Arbitrum and Solana; BTC, ETH, LTC, TRX, SOL, BNB, DOGE, and every other coin from [`/info/`](/docs/api/coins/get-all-info). - **Checkout:** works with the classic checkout and the Cart and Checkout blocks, and with High-Performance Order Storage. - **Payment page:** amount and address with copy buttons, QR code, a warning to send on the right network, a quote countdown, and a live status that moves from waiting to payment seen to confirmed. - **Order status:** the order goes on hold as soon as the payment is seen on chain, and to processing (or completed) once it is forwarded to your wallet. Each step adds an order note with the transaction ids. - **Edge cases:** - Partial payments add up. - An underpayment beyond your tolerance keeps the order on hold. - A payment made after the quote expired is checked against the current rate before the order is marked paid. - A payment to a cancelled order goes on hold for review. - **Security:** every callback is verified with the [RSA signature](/docs/callbacks) and a per-order secret, and processed once even when callbacks arrive together. Amounts use exact decimal math. ## Settings | Setting | Default | What it does | | ---------------------- | ---------- | ----------------------------------------------------------- | | Wallets | empty | Your receiving address per coin; a coin is offered when set | | Underpayment tolerance | 0.5% | How much less than the quote still marks the order paid | | Quote validity | 30 minutes | How long the converted amount is honoured | | Paid status | processing | Status given to a paid order | | Debug log | off | Writes to WooCommerce > Status > Logs | Your store currency must be one NoncePay converts (USD, EUR, GBP and about thirty others); the settings page warns you when it is not. ## Behind a proxy or CDN The plugin checks each callback's signature against the URL it registered for the order, so it keeps working with a CDN in front of WordPress. A proxy that rewrites the host or path before WordPress sees it needs the `noncepay_callback_base_url` filter: return the public scheme and host of your site. Not on WooCommerce? The [PHP guide](/docs/guides/php) and the [runnable examples](https://github.com/Nonce-pay/noncepay-examples) show the same flow in plain PHP and Laravel. # Introduction NoncePay accepts crypto payments on Bitcoin, Litecoin, Bitcoin Cash, Dogecoin, Solana, Ethereum, BNB Smart Chain, Polygon, Base, Arbitrum, Optimism, Avalanche and Tron, and forwards each one on-chain to the wallet you choose as soon as it confirms. There is no account to open and no API key: one request gives you a deposit address, and your server is told about every payment through a signed callback. ## How it works 1. **Your server creates an address** with [`/{ticker}/create/`](/docs/api/payments/create-address), passing your callback URL and your wallet. 2. **Your customer pays it.** If you asked for it, we call your URL with `pending=1` as soon as the payment is seen. 3. **We forward it.** Once the payment has the confirmations its network needs, the amount minus our fee and the network cost is sent to your wallet. 4. **You get a signed callback** with `pending=0`, the transaction ids and the amounts. Answer `*ok*`. ## Conventions - Base URL: `https://api.noncepay.app` - Every endpoint is a `GET` with query parameters, and every path ends with `/`. - No API key. The callback URL is the identity of a payment address: the same callback always returns the same address. - Success answers `200` with `"status": "success"`. Errors answer `400` (or `404`) with `{"status":"error","error":"…"}`. - Amounts are strings, so no precision is lost. # Quickstart ## 1. Create an address Call `/{ticker}/create/` with the URL that should be notified and the wallet that should receive the money. Put your own order id in the callback URL: it comes back on every callback. ```bash title="Terminal" curl "https://api.noncepay.app/trc20/usdt/create/?callback=https%3A%2F%2Fshop.example%2Fpaid%3Forder%3D1042&address=TJzXZQhbhJB3dA2TmxmvyPxk2nKsmZ6yWf&pending=1" ``` ```json title="Response" { "status": "success", "address_in": "TN7Zbu4UwtKneYDJyDKXkSorVu95hsE3q4", "address_out": "TJzXZQhbhJB3dA2TmxmvyPxk2nKsmZ6yWf", "callback_url": "https://shop.example/paid?order=1042", "minimum_transaction_coin": "10.00000000", "priority": "default" } ``` Show `address_in` to your customer, with a QR code from [`/{ticker}/qrcode/`](/docs/api/utilities/get-qr-code) if you like. Payments below `minimum_transaction_coin` are recorded but never forwarded. ## 2. Receive the callback When the payment arrives (with `pending=1`) and again once it has been forwarded, we call your URL: ```text GET https://shop.example/paid?order=1042&uuid=…&address_in=TN7Z…&address_out=TJzX… &txid_in=…&txid_out=…&value_coin=250&value_forwarded_coin=244.39&fee_coin=2.5 &coin=trc20_usdt&price=1&pending=0&result=sent ``` ## 3. Verify and acknowledge Check the `x-ca-signature` header with our public key, mark the order paid when `pending=0`, and answer `*ok*`. ```js title="server.js (Express)" import { createVerify } from 'node:crypto' import express from 'express' const { pubkey } = await fetch('https://api.noncepay.app/pubkey/').then((r) => r.json()) const app = express() app.get('/paid', (req, res) => { const url = `https://${req.headers.host}${req.originalUrl}` const valid = createVerify('sha256') .update(url) .verify(pubkey, req.headers['x-ca-signature'], 'base64') if (!valid) return res.status(401).end() if (req.query.pending === '0') { // value_forwarded_coin has reached your wallet: fulfil order req.query.order } res.send('*ok*') }) ``` A payment can be delivered more than once (retries). `uuid` is identical on the pending and the confirmed callback of one payment: store it and ignore repeats.