Overview
Accept crypto on your store with a hosted checkout. All endpoints live under https://api.cryptap.xyz/api/commerce. Authenticate server-to-server with a secret API key (created in the dashboard) as a Bearer token.
CrypTap is non-custodial: each checkout maps to a per-payment invoice contract, and funds settle on-chain straight to your wallet. CrypTap never holds, signs for, or transmits your funds.
Quickstart
Create a checkout and redirect the shopper to its hosted URL:
curl -X POST https://api.cryptap.xyz/api/commerce/checkouts \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"amount": 49.99,
"currency": "USD",
"reference": "order_1234",
"success_url": "https://store.example/thanks",
"cancel_url": "https://store.example/cart",
"metadata": {"order_id": 1234}
}'{
"success": true,
"checkout": {
"id": "cs_…",
"url": "https://commerce.cryptap.xyz/checkout/cs_…",
"status": "open",
"amount": 49.99,
"currency": "USD"
}
}Redirect the shopper to checkout.url.
Hosted checkout
On the hosted checkout the shopper picks a chain and a token, then pays in whichever way suits them:
- Browser wallet — one-click pay with an injected wallet (MetaMask, Rabby, Phantom for Solana).
- WalletConnect — scan a QR with any mobile wallet (EVM).
- QR / copy address — scan a payment QR or copy the address into any wallet.
The page polls for status and redirects to your success_url once the payment settles. You can also embed it in an iframe and listen for a PAYMENT_COMPLETE postMessage.
Webhooks
Register a webhook endpoint in the dashboard (Commerce → Webhooks). We POST signed events as they happen:
checkout.completed— settled on-chain to your wallet. Fulfill the order on this event.checkout.pending— payment seen on-chain, not yet settledcheckout.underpaid/checkout.overpaid— paid outside the ±0.25% tolerance; awaiting a refund addresscheckout.refunded— a mis-paid checkout was refunded to the shoppercheckout.wrong_chain— the payment landed on a different network than the one the shopper chosecheckout.recovered— funds that arrived off-network were returned; the order does not settle from this eventcheckout.expired— the payment window closed unpaidcheckout.failed— settlement failed
Every delivery has the same envelope. The order details you care about live under data:
POST https://your-store/your-webhook
CrypTap-Signature: t=1750000000,v1=9f86d081…
{
"id": "evt_4f3c…",
"event": "checkout.completed",
"created": 1750000000,
"data": {
"checkout_id": "cs_…",
"reference": "order_1234",
"amount": 49.99,
"currency": "USD",
"status": "completed",
"metadata": { "order_id": 1234 },
"network": "polygon",
"token": "USDC",
"crypto_amount": "49990000",
"contract_address": "0x…",
"transaction_hash": "0x…"
}
}Each request carries a CrypTap-Signature: t=<ts>,v1=<hex> header, wherev1 is HMAC_SHA256(secret, "<t>.<rawBody>"). Verify it against the raw request body (not a re-serialized object) before trusting the event, and return a2xx to acknowledge it — non-2xx responses are retried with exponential backoff:
import crypto from 'crypto';
function verify(secret, header, rawBody) {
const { t, v1 } = Object.fromEntries(header.split(',').map(p => p.split('=')));
const expected = crypto.createHmac('sha256', secret)
.update(`${t}.${rawBody}`).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
return ok && Math.abs(Date.now()/1000 - Number(t)) < 300; // reject stale
}Webhooks are idempotent targets: the same event id may arrive more than once on retry, so key your fulfillment off checkout_id + event and ignore duplicates.
WooCommerce
The WooCommerce plugin connects your store in three steps — no code required:
- Install the CrypTap Commerce plugin and enable it under WooCommerce → Settings → Payments.
- In the CrypTap dashboard, create a live secret key (Commerce → API Keys) and add a webhook endpoint (Commerce → Webhooks) with this URL:
Copy the signing secret it returns.https://YOUR-STORE/wc-api/cryptap_commerce - Back in the plugin settings, paste the secret API key and the webhook signing secret. Save.
Why is there no webhook URL field in the plugin?
Because you don't need one. WooCommerce automatically exposes the webhook endpoint at /wc-api/cryptap_commerce on your store. You just register that URL in the CrypTap dashboard and paste the signing secret into the plugin so it can verify the events we send. The plugin receives events at that route automatically.
Gas tank
A permissionless keeper settles paid checkouts on-chain and is reimbursed the gas it spends from a per-chain tank you pre-fund in the dashboard (Commerce → Gas Tank). The tank only ever pays settlement gas, and only you can withdraw it.
Fund it with a browser wallet or, with no extension, over WalletConnect. If a chain's tank drops below its minimum, new checkouts on that chain pause until you top it up.
Going live
- Add a payout wallet and fund the gas tank for each chain you accept (dashboard → Commerce → Gas Tank).
- Create a live API key.
- Register your webhook endpoint and verify signatures.
API reference
Base URL https://api.cryptap.xyz/api/commerce. All endpoints below are server-to-server and require Authorization: Bearer sk_live_… (or sk_test_…). Send and expect JSON. Every response includes a success boolean; on failure it also carries an error string and a non-2xx status.
Create checkout
POST /checkouts — create a hosted checkout for an order total.
amount(number, required) — order total incurrency.currency(string) — fiat code, defaults toUSD.reference(string) — your order id; echoed back in webhooks.success_url/cancel_url(string) — where the shopper returns.metadata(object) — arbitrary JSON, returned verbatim in webhooks.
curl -X POST https://api.cryptap.xyz/api/commerce/checkouts \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "amount": 49.99, "currency": "USD", "reference": "order_1234" }'Retrieve checkout
GET /checkouts/{id} — current status and details. Use this as a webhook fallback (e.g. on your thank-you page); webhooks remain the recommended path.
curl https://api.cryptap.xyz/api/commerce/checkouts/cs_… \
-H "Authorization: Bearer sk_live_..."{
"success": true,
"checkout": {
"id": "cs_…",
"url": "https://commerce.cryptap.xyz/checkout/cs_…",
"status": "completed",
"amount": 49.99,
"currency": "USD",
"reference": "order_1234"
}
}Checkout statuses
open— created; the shopper hasn't chosen a payment yet.pending— payment option selected; awaiting funds / settlement.completed— settled on-chain to your wallet. Fulfill here.underpaid/overpaid— out of tolerance; awaiting a refund address.refunded— a mis-paid checkout was returned to the shopper.expired— closed unpaid (default window ~20 min).
Test vs live
Keys are environment-scoped: sk_test_… for sandbox, sk_live_… for production. A checkout belongs to the mode of the key that created it, and only that key can read it back.
Settlement detail. Funds go from the shopper to the per-payment contract and are split (your payout + protocol fee) by an on-chain settle(). That call is permissionless and chain-gated: anyone may make it, it only runs on the chain the payment was created for, and it has no caller-supplied destination — the payout and fee addresses are fixed when the payment address is derived, so the worst a stranger can do is pay gas to send you your own money. A keeper triggers it and is reimbursed from your gas tank.
Mis-payments. Anything outside the ±0.25% tolerance, or that arrives on the wrong network, is held rather than settled, and returning it needs a signed authorization from CrypTap. That is the one place we act on a payment, and it is limited to funds that are already stuck: it cannot redirect a normal settlement, which pays only the addresses baked into the payment address itself.