Developer documentation

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.

1. Create an account & store

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 →

2. Get your credentials

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).

3. Base URL & authentication

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.

4. Create an invoice

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:

chainnetwork
btcBitcoin (on-chain)
lightningBitcoin via Lightning
txcTEXITcoin
ethEthereum (ETH + USDC/USDT/PYUSD/DAI)
baseBase (ETH + stablecoins)
tronTron (TRX + USDT)
solSolana (SOL + SPL tokens)
dogeDogecoin
ltcLitecoin
bchBitcoin Cash
dashDash
iskIskander Network
zcuZCU (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.

5. Check invoice status (polling)

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:

statusmeaning
pendingCreated; waiting for payment (or for the customer to pick a chain).
underpaidA payment arrived but is short of the requested amount.
confirmedPaid and confirmed on-chain. Treat this as final.
expiredTTL elapsed with no (sufficient) payment.

6. Receive webhooks

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/1

Events and when they're sent:

EventWhen / what to do
invoice.paidFull payment received (data.status = "confirmed"). Always followed by invoice.confirmed.
invoice.overpaidCustomer paid more than asked (data.status = "overpaid"). Also followed by invoice.confirmed.
invoice.confirmedFINAL — mark the order paid / ship. Sent for every settled invoice (status confirmed or overpaid). Settle on this one event.
invoice.underpaidPayment 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"
  }
}
FieldMeaning
idUnique event id (uuid). Dedupe on this — same as the X-TXCPay-Event-Id header.
typeOne of the four events above. Same as the X-TXCPay-Event header.
created_atWhen we sent it (ISO 8601, UTC).
data.invoice_idNectarPay invoice id — the id returned when you created the invoice.
data.store_idYour store's id.
data.statusconfirmed | overpaid | underpaid.
data.chainNetwork paid on: btc, txc, eth, base, tron, sol, ltc, doge, bch, dash, isk, zcu.
data.addressReceiving address in your wallet.
data.fiat_amountInvoice amount you asked for (number).
data.fiat_currencyCurrency of fiat_amount, e.g. USD.
data.paid_amount_usdUSD value we actually received (can be slightly under within 1%, or over).
data.order_idThe 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_..."

7. Drop-in JS button (no backend code)

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.

Live demo

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.

8. Email an invoice to the customer

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/..." }

9. List invoices (reconciliation)

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 paramMeaning
statuspending, detected, underpaid, confirmed, overpaid, expired, cancelled, failed
chainbtc, txc, eth, base, sol, tron, ltc, doge, bch, dash, isk, zcu, lightning
order_idExact match on your own order reference
since / untilISO-8601 timestamps filtering created_at
limit / offsetlimit 1–200 (default 50), offset for paging

10. Webhook delivery history

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": "..." } ] }

11. Rates

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 } ] }

12. Store info

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" } ] }

13. Hosted payment links (no code at all)

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.

E-commerce plugins

Prefer a ready-made plugin? WooCommerce, PrestaShop, OpenCart, Magento, WHMCS and more → Every plugin is a thin wrapper around the exact API documented above.

Don't trust us? Good.

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.

Prefer to self-host the GitHub source? Go for it — no license fee, no phone-home. The $999 remix is just the shortcut.