Accept crypto in one API call.
Create an invoice from your server, hand the customer a hosted checkout, and receive a signed webhook the moment it settles. Twelve assets across five chains — behind one Bearer key.
Two ways to get a key
Every request to the API is authenticated with an API key. Pick whichever fits you — both mint a live pck_ key you use the same way.
Option A — Email account (recommended)
- Register at /signup with an email address.
- Verify your email from the link we send — an unverified account cannot create invoices or mint a key.
- Generate an API key from your dashboard. It is issued after verification and shown once.
Option B — Instant, no email
Register a pseudonymous merchant in a single call. No email, a flat 1% fee, and the key is returned inline — shown once.
curl -X POST https://privcart.xyz/v1/merchants \
-H "Content-Type: application/json" \
-d '{
"name": "Your store",
"allowed_origins": ["https://yourstore.example"]
}'
{
"api_key": "pck_…",
"merchant_id": "…",
"name": "Your store",
"fee_bp": 100
}
allowed_origins lists the site origins allowed to embed your hosted checkout in an <iframe> (see Checkout & embed). Supply at least one bare origin — scheme + host, no path or wildcards. fee_bp: 100 is the 1% fee in basis points.
api_key straight into your server's secret manager before you close the response. Lost it? Mint a new one and the old one stops working.One bearer key, server-side only
Send your key as a Bearer token on every /v1/* request. A missing or invalid key returns 401.
GET /v1/invoices
Host: privcart.xyz
Authorization: Bearer pck_…
Public exceptions
Two surfaces need no key — they're what your customer touches:
- The hosted checkout page —
GET /checkout/{invoiceId}renders the QR, address, and live status. - Its status stream —
GET /v1/invoices/{id}/streamis an SSE feed withAccess-Control-Allow-Origin: *.
Both are gated by the unguessable invoice id (122-bit), so a customer can pay without ever seeing your key.
Create an invoice
Ask for an asset and an amount; PrivCart returns a unique receive address and a checkout you can hand to the customer.
- asset requiredOne of the twelve supported symbols, e.g.
BTC. - amount requiredA string in the asset's main unit (string avoids float rounding), e.g.
"0.0025". - order_id optionalYour own reference, echoed back and in webhooks.
- callback_url optionalHTTPS endpoint for signed webhooks.
- ttl_ms optionalTime-to-pay in ms (1000 – 86 400 000). Defaults apply if omitted.
curl -X POST https://privcart.xyz/v1/invoices \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"asset": "BTC",
"amount": "0.0025",
"order_id": "order-1042",
"callback_url": "https://yourstore.example/webhooks/privcart"
}'
{
"id": "0da553a9-e7f0-4b72-b050-0fdd0db1ccca",
"asset": "BTC",
"amount": "0.0025",
"receive_address": "bc1q…",
"status": "pending",
"confirmations": 0,
"min_confirmations": 2,
"expires_at": 1754064000000
}
POST is blocked by CORS and would leak your key. The receive_address is unique to this invoice — show it yourself, or just redirect to the hosted checkout below.Hosted checkout & embed
PrivCart hosts the payment page — a QR, the exact pay-to address, and a status that updates live as the payment is detected and confirmed. Point the customer at it, or embed it.
Redirect to the hosted page
https://privcart.xyz/checkout/0da553a9-e7f0-4b72-b050-0fdd0db1ccca
Embed in your page
<iframe
src="https://privcart.xyz/checkout/INVOICE_ID"
title="Pay with PrivCart"
width="100%" height="640"
style="max-width:460px;border:0;border-radius:16px"
allow="clipboard-write">
</iframe>
allowed_origins at registration so the checkout accepts being framed there.No-code pay links (no server required)
Create a pay link for a product in your dashboard — a label, an asset, and a fixed price (or leave the price open for donations). You get a permanent link you can embed anywhere. When a customer opens it, PrivCart mints the invoice server-side and shows the checkout — you never touch an API key and need no backend.
<iframe src="https://privcart.xyz/l/LINK_ID"
title="Pay with crypto" width="100%" height="540"
style="max-width:420px;border:0;border-radius:12px"></iframe>
Prefer a button, or a script that mounts it for you? The dashboard's embed builder gives you an <a> button and a PrivCart.payLink() script for the same link, each with a live preview and a one-click copy.
Copy prompt for AI
Not a developer? Every pay link ships with a ready-made AI prompt — copy it from the dashboard and paste it into ChatGPT, Claude, or your site builder's assistant. It tells the AI exactly where to place the button on the product page for your platform (WordPress / WooCommerce, Shopify, or custom) and never to alter the fixed price.
Status & live updates
Read the current state on demand, or subscribe to a stream and react the instant it changes.
Returns the full invoice — status, confirmations, tx_id, and more. Requires your key (server-side).
A Server-Sent Events stream of status updates. No key required, CORS * — safe to consume straight from the browser.
const es = new EventSource(
"https://privcart.xyz/v1/invoices/INVOICE_ID/stream"
);
es.onmessage = (ev) => {
const { status, confirmations, min_confirmations } = JSON.parse(ev.data);
render(status, confirmations, min_confirmations);
if (status === "settled" || status === "expired") es.close();
};
es.onerror = () => {
// transient — the browser reconnects automatically
};
Signed webhooks
Set a callback_url when you create an invoice (or in dashboard settings). On every status change, PrivCart POSTs a signed JSON callback to that URL.
POST /webhooks/privcart
X-PrivCart-Signature: sha256=<hmac-hex>
X-PrivCart-Event-Id: id_…
Content-Type: application/json
{
"id": "id_…",
"event": "invoice.settled",
"created_at": 1754064000000,
"data": {
"invoice": {
"id": "0da553a9-e7f0-4b72-b050-0fdd0db1ccca",
"asset": "BTC",
"amount": "0.0025",
"status": "settled",
"confirmations": 2,
"min_confirmations": 2
}
}
}
Verify before you trust it
The signature is sha256= + HMAC-SHA256 of the raw request body, keyed by your webhook signing secret. Compute it and compare in constant time before parsing the body.
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the exact bytes PrivCart POSTed — verify BEFORE JSON.parse
export function verify(rawBody, signatureHeader, secret) {
const expected =
"sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(signatureHeader ?? "");
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}
Rotate the signing secret
curl -X POST https://privcart.xyz/v1/webhooks/rotate-secret \
-H "Authorization: Bearer YOUR_API_KEY"
# → { "webhook_secret": "wcs_…" } (shown once)
X-PrivCart-Event-Id. The new secret from a rotation is shown once, just like an API key.Twelve supported assets
Five native chains plus seven ERC-20 tokens. Confirmation depth is chain-appropriate; times are typical, not guaranteed.
| Asset | Name | Network | Confirmations | ~ Confirm time |
|---|---|---|---|---|
| BTC | Bitcoin | Bitcoin | 2 | ~20 min |
| ETH | Ethereum | Ethereum | 12 | ~3 min |
| SOL | Solana | Solana | 1 | ~1 sec |
| XMR | Monero | Monero | 10 | ~20 min |
| LTC | Litecoin | Litecoin | 3 | ~8 min |
| USDC | USD Coin | Ethereum · ERC-20 | 12 | ~3 min |
| USDT | Tether | Ethereum · ERC-20 | 12 | ~3 min |
| DAI | Dai | Ethereum · ERC-20 | 12 | ~3 min |
| USDS | Sky Dollar | Ethereum · ERC-20 | 12 | ~3 min |
| WBTC | Wrapped BTC | Ethereum · ERC-20 | 12 | ~3 min |
| XAUT | Tether Gold | Ethereum · ERC-20 | 12 | ~3 min |
| ZCHF | Swiss Franc | Ethereum · ERC-20 | 12 | ~3 min |
Fees & settlement
New accounts pay 1% per transaction (fee_bp: 100). The fee is booked to a ledger as each payment settles — never skimmed mid-transfer — so your available balance is always gross receipts minus accrued fees.
Custodial-lite. PrivCart derives a unique HD receive address per invoice. Funds land there, are swept to treasury once confirmed on-chain, and credited to your merchant balance. You withdraw available balances from the merchant dashboard to an address you control.
Go-live checklist
Four things to confirm before you take real money.
- Key stored server-side. In a secret manager or environment variable — never in a browser bundle, mobile app, or the checkout iframe.
- callback_url set & signature verified. Your handler validates the raw-body HMAC in constant time before it fulfils an order.
- Tested on a small real amount. One end-to-end run: create → pay → settled → webhook → withdraw.
- Dashboard bookmarked. For monitoring invoices and withdrawing your balances.