PayWicket Developer & Merchant Docs
Accept stablecoin payments (USDC on Polygon) with a familiar checkout, automatic on-chain settlement to a wallet you control, signed webhooks, and one-click reconciliation. PayWicket is non-custodial — funds never sit with us.
Base URL: https://paywicket.com
Overview
PayWicket turns a crypto payment into an ordinary checkout. A buyer completes a payment with a wallet or a card; USDC settles directly to the merchant's payout address on Polygon through an audited on-chain router that splits the platform fee automatically; and your system receives a signed payment.confirmed webhook so you can fulfil the order.
- Non-custodial by design. Each merchant controls the destination wallet; PayWicket never holds or moves customer funds off its rails.
- Stablecoin settlement. Payments settle in USDC (6-decimal ERC-20) on Polygon — final, low-fee, and 1:1 with USD.
- Card-friendly for buyers. Buyers without crypto can fund with a debit/credit card via a licensed on-ramp, or create a wallet with just an email — the crypto layer stays invisible to them.
- Refundable. Confirmed payments can be returned (ERC-20 chargeback) to the payer or any address.
How a payment flows
- Your backend calls
POST /api/paymentswith the USD amount and an optionalcallbackUrlandidempotencyKey. You get back a payment object with a hostedcheckoutUrl. - You redirect the customer to
checkoutUrl(or embed it). They pay with a wallet (gasless), a card-funded wallet, or an email-created wallet. - The
PaymentRoutercontract transfers USDC to the merchant payout address and sends the platform fee to the treasury in the same transaction. APaidevent is indexed and matched to your payment by itspaymentRef. - PayWicket marks the payment confirmed and fires a signed
payment.confirmedwebhook to yourcallbackUrl. - You reconcile via the webhook, the
GET /api/payments/:idread, or the ledger CSV export — each carries the on-chain tx hash and a block-explorer link.
Quickstart
1) Create an account and a wallet in the dashboard — the wallet is issued a scoped API key. 2) Create a payment and redirect the buyer:
curl -X POST https://paywicket.com/api/payments \
-H "x-api-key: $PAYWICKET_API_KEY" \
-H "content-type: application/json" \
-d '{
"amountUsd": 49.99,
"currency": "USDC",
"description": "Order #1234",
"customerEmail": "buyer@example.com",
"callbackUrl": "https://yourstore.com/webhooks/paywicket",
"idempotencyKey": "order-1234",
"metadata": { "orderId": "1234" }
}'
Response (trimmed):
{
"id": "pay_9f2c1a8b7d6e5f40",
"status": "pending",
"amountUsd": 49.99,
"currency": "USDC",
"amountToken": { "human": "49.99", "base": "49990000" },
"checkoutUrl": "https://paywicket.com/checkout.html?pid=pay_9f2c1a8b7d6e5f40",
"expiresAt": 1734112800000
}
Send the customer to checkoutUrl. When they pay, you'll receive a payment.confirmed webhook.
Authentication
There are two credential types:
- Wallet API key — sent as the
x-api-keyheader. Used to create and read payments for that wallet. Issued per wallet and shown in the dashboard. - Account bearer token — from
POST /api/auth/login, sent asAuthorization: Bearer <token>. Used for management endpoints (creating wallets, issuing refunds, analytics). Supports TOTP MFA.
Payments API
Create a payment intent. Authenticate with x-api-key.
| Field | Type | Notes |
|---|---|---|
amountUsd | number | Required. > 0, ≤ 1,000,000. USD value; converted to token at current rate. |
currency | string | USDC (default) or MATIC. |
description | string | Shown on checkout; ≤ 200 chars. |
customerName, customerEmail | string | Optional; stored for reconciliation/receipts. |
callbackUrl | string | HTTPS endpoint to receive webhooks. Falls back to the wallet's default webhook. |
idempotencyKey | string | Repeated create with the same key returns the existing payment — safe on retries. |
metadata | object | Free-form JSON echoed back on reads and webhooks. |
Returns a payment object: id, status, amountUsd, currency, amountToken {human, base}, address, checkoutUrl, paymentUri, qrDataUrl, metadata, createdAt, expiresAt, settlement, and (in on-chain mode) an onchain block with router/token/paymentRef.
Fetch current state of a payment (poll as a fallback to webhooks).
List payments for a wallet.
Public runtime config: active network, chainId, settlement token (symbol/address/decimals), current platformFeeBps, and paymentExpiryMinutes.
Payment states
| State | Meaning | Transition |
|---|---|---|
| pending | Created, awaiting payment. | Initial state. |
| confirmed | Funds received on-chain and matched. | From pending when the router Paid event (or balance) is observed. Fires payment.confirmed. |
| expired | Not paid before expiresAt. | From pending after the expiry window (default 30 min). |
| refunded | Returned to the payer. | From confirmed via refund. Fires payment.refunded. |
Webhooks
PayWicket POSTs a JSON event to your callbackUrl on state changes. Events: payment.confirmed, payment.refunded.
| Header | Value |
|---|---|
x-cryptopay-event | The event type, e.g. payment.confirmed. |
x-cryptopay-signature | HMAC-SHA256 hex digest of the raw request body, keyed with your webhook secret. |
Body:
{
"type": "payment.confirmed",
"sentAt": "2026-08-20T14:03:11.204Z",
"data": {
"id": "pay_9f2c1a8b7d6e5f40",
"status": "confirmed",
"amountUsd": 49.99,
"currency": "USDC",
"metadata": { "orderId": "1234" },
"settlement": {
"txHash": "0xabc…",
"fromAddress": "0x…",
"amountReceivedBase": "49990000",
"confirmedAt": 1734112991204,
"explorerUrl": "https://polygonscan.com/tx/0xabc…"
}
}
}
Verify the signature (Node)
import { createHmac, timingSafeEqual } from "node:crypto";
app.post("/webhooks/paywicket",
express.raw({ type: "application/json" }), (req, res) => {
const sig = req.get("x-cryptopay-signature");
const expected = createHmac("sha256", process.env.PAYWICKET_WEBHOOK_SECRET)
.update(req.body) // raw bytes, not parsed JSON
.digest("hex");
const ok = sig && timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!ok) return res.status(400).send("bad signature");
const evt = JSON.parse(req.body);
if (evt.type === "payment.confirmed") { /* fulfil order evt.data.metadata.orderId */ }
res.sendStatus(200); // respond 2xx within ~5s
});
id / metadata, and treat webhooks as idempotent — the same event may be delivered more than once. Always confirm state with GET /api/payments/:id before shipping high-value goods.Reconciliation
Every confirmed/refunded payment carries the on-chain settlement.txHash and an explorerUrl, so your books tie directly to the ledger. Options:
- Real-time: the confirmation webhook (with tx hash + amount received).
- Pull:
GET /api/payments/:idorGET /api/merchants/:id/payments. - Batch/accounting: CSV export below.
Columns: date_received, customer_name, customer_email, cryptocurrency_received, amount_crypto, usd_value_at_receipt, wallet_transaction_id, receiving_address, status, refund_tx, description.
Errors & rate limits
Errors return a non-2xx status with a JSON body { "error": "message" }.
| Status | Meaning |
|---|---|
400 | Invalid request (e.g. bad amountUsd, missing fields). |
401 | Unknown/missing credential (send x-api-key or bearer token). mfa_required when TOTP is needed. |
403 | Not permitted (e.g. refunding a payment you don't own). |
404 | Resource not found. |
429 | Rate limited. Auth and write endpoints (payments/refunds) are throttled per IP. |
Use idempotencyKey on create to make retries safe. Webhook receivers should always return quickly (2xx within ~5s) and process asynchronously.
Payment methods & currencies
- Settlement currency: USDC on Polygon (also supports native MATIC). USDC is a 6-decimal ERC-20 pegged 1:1 to USD.
- Existing wallet (gasless): buyers sign an EIP-3009 authorization; a relayer pays the gas, so the buyer needs no MATIC.
- Card → USDC: buyers without crypto fund the payment with a debit/credit card via a licensed on-ramp (Coinbase). PayWicket never touches card data.
- Email-created wallet: buyers with no wallet can create a self-custodial wallet with an email (embedded wallets) and then fund it — the crypto stays invisible.
Settlement
Settlement is direct and non-custodial. When a payment confirms, the PaymentRouter smart contract, in a single transaction:
- transfers the net USDC to the merchant's payout address (either a PayWicket-generated wallet you control the keys to, or your own bring-your-own address), and
- routes the platform fee to the treasury. The fee is
max(percentage, minimum floor); the current percentage is returned byGET /api/config(platformFeeBps).
There is no T+N payout delay and no pooled balance — funds arrive at the merchant address as the transaction confirms (seconds on Polygon).
Refunds & disputes
Authenticate with the account bearer token. Only confirmed payments can be refunded, once. Body accepts an optional toAddress (defaults to the original payer) and, for non-custodial refunds, an txHash you recorded after sending USDC back from your own wallet.
payment.refunded.Sandbox & testing
PayWicket runs in three modes so you can integrate before touching real funds:
- Simulation — end-to-end flow with fabricated confirmations, no chain. Ideal for wiring your webhook handler and order fulfilment.
- Testnet (Polygon Amoy) — real on-chain settlement with test USDC. A faucet endpoint issues demo USDC so you can complete a real signed payment.
- Production (Polygon mainnet) — live USDC.
Ask us to provision a sandbox wallet + API key, or self-serve from the dashboard. The recommended acceptance test: create a payment → complete it on the hosted checkout → assert your endpoint received a valid, signature-verified payment.confirmed → reconcile it.
Integration options
- Hosted checkout — redirect to
checkoutUrl; zero UI to build. Supports a return-to-store URL. - Drop-in SDK —
/sdk/cryptopay.jsfor an embedded pay button. - WooCommerce plugin — native "Pay with crypto" gateway.
- Agent-native —
x402(HTTP 402) for machine-to-machine payments and an MCP server exposing PayWicket as agent tools.
Merchant technical requirements
- An HTTPS webhook endpoint that verifies the HMAC signature and returns 2xx quickly.
- A payout wallet address (bring-your-own recommended) or use a PayWicket-generated wallet whose recovery phrase you export and secure.
- Server-side storage of your wallet API key and webhook secret.
- Idempotent order handling keyed on the payment
id/ youridempotencyKey. - Reconciliation against on-chain
txHashfor finance/audit.
Questions or need a sandbox key? Email juan.lavieri@gmail.com. Some deeper architecture material (settlement internals, custom routing, commercial terms) is shared under NDA.