A non-custodial gateway. You hold the keys; we derive deposit addresses, watch the chain, and notify your store. This page is the complete public API reference — everything a third-party site needs to integrate without ever talking to us.
Sign up at /auth, create a store, and add at least one chain config.
New to xpubs? Step-by-step wallet setup guide for every supported chain → Wondering whether each invoice gets its own address? Address derivation & rotation policy →
Each store has its own secret API key (sk_live_…), created from Store → API keys. It's shown once — store it securely.
For webhooks, set your endpoint URL and copy the signing secret from Store → Webhooks. The secret is per-store and is used to verify every event we send you (see §6).
All API calls go to https://app.nectar-pay.com. Keys are issued and validated on this host only — sending a valid key to nectar-pay.com (the marketing site) returns 401 Invalid API key.
Every request carries the key as a Bearer token. Keys are scoped to a single store: you can only ever see and create that store's invoices.
NECTARPAY_API_URL=https://app.nectar-pay.com
# sanity-check your key before wiring anything else:
curl -s https://app.nectar-pay.com/api/public/v1/me \
-H "Authorization: Bearer sk_live_..."A 200 from /api/public/v1/me confirms the key, the store it belongs to, and the webhook URL currently configured. All errors across the API look like { "error": "human readable message" } with an appropriate 4xx/5xx status.
POST https://app.nectar-pay.com/api/public/v1/invoices
Authorization: Bearer sk_live_...
Content-Type: application/json
{
"chain": "btc", // optional — omit to let the customer pick
"amount": 49.00, // required, fiat amount
"currency": "USD", // required, ISO fiat code
"order_id": "ORDER_1234", // optional, echoed back + sent in webhooks
"description": "T-shirt, size M", // optional
"redirect_url": "https://store.example.com/thanks", // optional
"buyer_email": "sam@example.com", // optional, sends a receipt
"expires_in_seconds": 900 // optional, 60–86400, default is store TTL
}Supported chain values:
| chain | network |
|---|---|
| btc | Bitcoin (on-chain) |
| lightning | Bitcoin via Lightning |
| txc | TEXITcoin |
| eth | Ethereum (ETH + USDC/USDT/PYUSD/DAI) |
| base | Base (ETH + stablecoins) |
| tron | Tron (TRX + USDT) |
| sol | Solana (SOL + SPL tokens) |
| doge | Dogecoin |
| ltc | Litecoin |
| bch | Bitcoin Cash |
| dash | Dash |
| isk | Iskander Network |
| zcu | ZCU (Zero Chill) |
If you omit chain, the invoice is created in a "customer picks the network" state and address/crypto_amount/rate are null until the customer chooses on the hosted checkout page.
Response (201):
{
"id": "7c9e…-invoice-uuid",
"address": "bc1q…", // null if chain omitted
"crypto_amount": 0.00041712, // null if chain omitted
"rate": 117500.00, // fiat per coin, null if chain omitted
"fiat_amount": 49.00,
"currency": "USD",
"chain": "btc",
"status": "pending",
"expires_at": "2026-09-14T10:15:00.000Z",
"checkout_url": "https://app.nectar-pay.com/i/7c9e…"
}Send the customer to checkout_url (QR + amount + copyable address) or render your own UI from address and crypto_amount.
GET https://app.nectar-pay.com/api/public/v1/invoices/{id}
Authorization: Bearer sk_live_...
→ 200 {
"id": "7c9e…",
"status": "confirmed", // see lifecycle below
"chain": "btc",
"fiat_amount": 49.00,
"fiat_currency": "USD",
"crypto_amount": 0.00041712,
"rate": 117500.00,
"address": "bc1q…",
"order_id": "ORDER_1234",
"description": "…",
"redirect_url": "…",
"buyer_email": "…",
"expires_at": "…",
"created_at": "…",
"checkout_url": "https://app.nectar-pay.com/i/7c9e…"
}A 404 means the invoice doesn't exist or belongs to a different store's key — we never leak across stores.
Invoice lifecycle:
| status | meaning |
|---|---|
| pending | Created; waiting for payment (or for the customer to pick a chain). |
| underpaid | A payment arrived but is short of the requested amount. |
| confirmed | Paid and confirmed on-chain. Treat this as final. |
| expired | TTL elapsed with no (sufficient) payment. |
We POST a signed JSON event to your configured webhook URL on every status change. Headers:
POST /your/webhook HTTP/1.1
Content-Type: application/json
X-TXCPay-Signature: t=1729000000,v1=<hex-hmac-sha256>
X-TXCPay-Event: invoice.paid
X-TXCPay-Event-Id: 0f4c…-event-uuid
User-Agent: payHME-webhook/1Events and when they're sent:
| Event | When / what to do |
|---|---|
| invoice.paid | Full payment received (data.status = "confirmed"). Always followed by invoice.confirmed. |
| invoice.overpaid | Customer paid more than asked (data.status = "overpaid"). Also followed by invoice.confirmed. |
| invoice.confirmed | FINAL — mark the order paid / ship. Sent for every settled invoice (status confirmed or overpaid). Settle on this one event. |
| invoice.underpaid | Payment arrived but short beyond the 1% cushion (data.status = "underpaid"). Don't ship. |
A real, complete paid notification — this is the exact JSON body, no other fields are sent:
POST https://your-site.com/your/webhook
Content-Type: application/json
X-TXCPay-Signature: t=1791019331,v1=5d2c0e9f6b1a…(64 hex chars)
X-TXCPay-Event: invoice.confirmed
X-TXCPay-Event-Id: 3b8f2a61-9d4e-4c1a-8f0b-2e7d5c9a1f44
User-Agent: payHME-webhook/1
{
"id": "3b8f2a61-9d4e-4c1a-8f0b-2e7d5c9a1f44",
"type": "invoice.confirmed",
"created_at": "2026-10-03T09:22:11.482Z",
"data": {
"invoice_id": "7c9e6f1a-2b3d-4e5f-8a9b-0c1d2e3f4a5b",
"store_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"status": "confirmed",
"chain": "txc",
"address": "TXc9yQ…(the address the customer paid)",
"fiat_amount": 49.00,
"fiat_currency": "USD",
"paid_amount_usd": 49.00,
"order_id": "ORDER_1234"
}
}| Field | Meaning |
|---|---|
| id | Unique event id (uuid). Dedupe on this — same as the X-TXCPay-Event-Id header. |
| type | One of the four events above. Same as the X-TXCPay-Event header. |
| created_at | When we sent it (ISO 8601, UTC). |
| data.invoice_id | NectarPay invoice id — the id returned when you created the invoice. |
| data.store_id | Your store's id. |
| data.status | confirmed | overpaid | underpaid. |
| data.chain | Network paid on: btc, txc, eth, base, tron, sol, ltc, doge, bch, dash, isk, zcu. |
| data.address | Receiving address in your wallet. |
| data.fiat_amount | Invoice amount you asked for (number). |
| data.fiat_currency | Currency of fiat_amount, e.g. USD. |
| data.paid_amount_usd | USD value we actually received (can be slightly under within 1%, or over). |
| data.order_id | The order_id you sent when creating the invoice, or null. Use it to find your order. |
Want to double-check? Fetch GET /api/public/v1/invoices/{invoice_id} and confirm status is confirmed or overpaid before shipping.
Always verify the signature against the raw request body before trusting anything:
// node:
const sig = req.headers["x-txcpay-signature"];
const [, t] = sig.match(/t=(\d+)/);
const [, v1] = sig.match(/v1=([a-f0-9]+)/);
const expected = crypto
.createHmac("sha256", WEBHOOK_SECRET) // Store → Webhooks
.update(`${t}.${rawBody}`)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) reject();
// php:
[$t, $v1] = sscanf($_SERVER['HTTP_X_TXCPAY_SIGNATURE'], 't=%d,v1=%s');
$expected = hash_hmac('sha256', "$t." . file_get_contents('php://input'), $WEBHOOK_SECRET);
if (!hash_equals($expected, $v1)) { http_response_code(401); exit; }Reject timestamps more than ~5 minutes old to stop replay. Events carry a unique id — dedupe on it, because retries can repeat a delivery. Reply 2xx quickly and process asynchronously.
Missed one? Trigger a redelivery for a single invoice:
curl -X POST "https://app.nectar-pay.com/api/public/v1/invoices/{id}?action=redeliver-webhook" \
-H "Authorization: Bearer sk_live_..."One <script> tag and a button — crypto checkout opens in a modal, no redirect, works on any site.
<script src="https://app.nectar-pay.com/sdk/payhme.js" defer></script>
<button
data-payhme
data-api-key="sk_live_..."
data-chain="btc"
data-amount="49.00"
data-currency="USD"
data-order-id="ORDER_1234"
>Pay $49 with Bitcoin</button>Or call it programmatically:
PayHME.checkout({
apiKey: "sk_live_...",
chain: "base", amount: 49, currency: "USD",
orderId: "ORDER_1234",
}).then(result => {
// result.status: "paid" | "closed" | "expired"
if (result.status === "paid") console.log("Got it:", result.tx);
});The button's result event is a UX signal, not a settlement guarantee — still confirm payment via webhook or the status endpoint before shipping goods.
Click below — this is the actual SDK, opening a real (test-mode) checkout in a modal. No payment will be processed; close the modal at any time.
Tip: open your browser console to see the payhme:result event payload when the modal closes.
Have us send the branded payment request by email. Uses the invoice's buyer_email unless you pass to. Sending an expiring invoice pushes its expiry out 7 days so the emailed link stays payable.
curl -X POST "https://app.nectar-pay.com/api/public/v1/invoices/{id}/email" \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"to":"customer@example.com"}'
# 200
{ "ok": true, "to": "customer@example.com", "sent_at": "...",
"send_count": 1, "expires_at": "...", "checkout_url": "https://app.nectar-pay.com/i/..." }Paged, newest first, scoped to your key's store. All filters are optional and combinable; status and chain accept comma-separated lists.
curl "https://app.nectar-pay.com/api/public/v1/invoices?status=confirmed,underpaid&since=2026-09-01T00:00:00Z&limit=100" \
-H "Authorization: Bearer sk_live_..."
# 200
{ "total": 214, "limit": 100, "offset": 0, "invoices": [ { "id": "...", "status": "confirmed",
"chain": "btc", "token": null, "fiat_amount": 49, "fiat_currency": "USD",
"crypto_amount": 0.00042, "order_id": "ORDER_1234", "created_at": "...",
"checkout_url": "..." } ] }| Query param | Meaning |
|---|---|
| status | pending, detected, underpaid, confirmed, overpaid, expired, cancelled, failed |
| chain | btc, txc, eth, base, sol, tron, ltc, doge, bch, dash, isk, zcu, lightning |
| order_id | Exact match on your own order reference |
| since / until | ISO-8601 timestamps filtering created_at |
| limit / offset | limit 1–200 (default 50), offset for paging |
Every attempt we made for an invoice — status code, retry schedule, and the first 500 bytes of your response. Handy when a webhook silently failed in production.
curl "https://app.nectar-pay.com/api/public/v1/invoices/{id}/webhooks" \
-H "Authorization: Bearer sk_live_..."
# 200
{ "invoice_id": "...", "deliveries": [ { "id": "...", "url": "https://you/hook",
"attempt": 1, "status_code": 200, "ok": true, "delivered_at": "...",
"next_retry_at": null, "event_type": "invoice.paid", "event_id": "..." } ] }The live USD rates we price invoices with. Pass ?amount= to get the converted crypto amount per network — useful for showing a price before creating an invoice. Stablecoins (USDC, USDT, TSD) are pegged at $1.
curl "https://app.nectar-pay.com/api/public/v1/rates?amount=25" \
-H "Authorization: Bearer sk_live_..."
# 200
{ "fiat": "USD", "amount": 25,
"rates": [ { "chain": "btc", "rate": 118422.5, "crypto_amount": 0.00021, "fetched_at": "..." } ],
"stablecoins": [ { "token": "USDC", "rate": 1, "crypto_amount": 25 } ] }What your key can see about the store it belongs to: currency, tax settings, default invoice lifetime, which networks and tokens are enabled, whether a webhook is configured, and the store's hosted payment links.
curl "https://app.nectar-pay.com/api/public/v1/store" \
-H "Authorization: Bearer sk_live_..."
# 200
{ "id": "...", "name": "Acme Coffee", "currency": "USD", "invoice_ttl_seconds": 900,
"tax_bps": 825, "webhook_configured": true,
"chains": [ { "chain": "btc", "network": "mainnet", "tokens": [] },
{ "chain": "base", "network": "mainnet", "tokens": ["USDC"] } ],
"payment_links": [ { "slug": "acme", "url": "https://app.nectar-pay.com/t/acme" } ] }A payment link is a reusable hosted page at https://app.nectar-pay.com/t/your-slug — put it in an email, a QR code, a bio link, a donation button. No API key needed by the payer. Create and manage them over the API:
# create
curl -X POST "https://app.nectar-pay.com/api/public/v1/links" \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"slug":"acme","title":"Acme Coffee","preset_amounts":[5,10,25],
"allow_custom_amount":true,"min_amount":1,"max_amount":500}'
# 201 -> { "slug": "acme", "url": "https://app.nectar-pay.com/t/acme", ... }
# list
curl "https://app.nectar-pay.com/api/public/v1/links" -H "Authorization: Bearer sk_live_..."
# update / deactivate
curl -X PATCH "https://app.nectar-pay.com/api/public/v1/links/acme" \
-H "Authorization: Bearer sk_live_..." -H "Content-Type: application/json" \
-d '{"title":"Acme Coffee — Tips"}'
curl -X DELETE "https://app.nectar-pay.com/api/public/v1/links/acme" \
-H "Authorization: Bearer sk_live_..."Omit slug and we derive one from the title (with a short suffix if it's taken). Deleting deactivates the link — the URL stops accepting payments but past invoices stay intact. Set is_donation: true for a donate-style page.
Prefer a ready-made plugin? WooCommerce, PrestaShop, OpenCart, Magento, WHMCS and more → Every plugin is a thin wrapper around the exact API documented above.
We get it. The whole point of crypto is don't trust, verify. A payment processor asking you to take its word for it is the exact thing we built Nectar.Pay to replace. We don't hold your keys — and you don't have to take our word for that either.
Read the source. Run it yourself. Or fork the whole thing and run a competing gateway — we honestly don't mind. The bees do better when the hive is bigger.
Every line of the gateway, SDK, and watcher. MIT-ish, audit-friendly, no hidden custody layer.
One-click clone of the entire Nectar.Pay stack into your own Lovable workspace. Your domain, your keys, your hive. Checkout via blockchainmint.com — coming soon.
Prefer to self-host the GitHub source? Go for it — no license fee, no phone-home. The $999 remix is just the shortcut.