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 a small on-chain router contract (source verified on Polygonscan) that splits the platform fee automatically; and your system receives a signed payment.confirmed webhook so you can fulfil the order.

How a payment flows

1. Create payment (API)→ 2. Buyer pays (hosted checkout)→ 3. On-chain settlement + fee split→ 4. Confirmation webhook→ 5. Reconcile / export
  1. Your backend calls POST /api/payments with the USD amount and an optional callbackUrl and idempotencyKey. You get back a payment object with a hosted checkoutUrl.
  2. You redirect the customer to checkoutUrl (or embed it). They pay with a wallet (gasless), a card-funded wallet, or an email-created wallet.
  3. The PaymentRouter contract transfers USDC to the merchant payout address and sends the platform fee to the treasury in the same transaction. A Paid event is indexed and matched to your payment by its paymentRef.
  4. PayWicket marks the payment confirmed and fires a signed payment.confirmed webhook to your callbackUrl.
  5. You reconcile via the webhook, the GET /api/payments/:id read, 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:

Treat the wallet API key like a password. It authorizes charge creation on your wallet. Keep it server-side; never ship it in browser code.

Payments API

POST /api/payments

Create a payment intent. Authenticate with x-api-key.

FieldTypeNotes
amountUsdnumberRequired. > 0, ≤ 1,000,000. USD value; converted to token at current rate.
currencystringUSDC (default) or MATIC.
descriptionstringShown on checkout; ≤ 200 chars.
customerName, customerEmailstringOptional; stored for reconciliation/receipts.
callbackUrlstringHTTPS endpoint to receive webhooks. Falls back to the wallet's default webhook.
idempotencyKeystringRepeated create with the same key returns the existing payment — safe on retries.
metadataobjectFree-form JSON echoed back on reads and webhooks.
expiresInMinutesnumberOptional. Default 30; from 5 up to 86,400 (60 days) for invoices and payment requests.

Returns a payment object: id, status, amountUsd, currency, amountToken {human, base}, address, checkoutUrl, checkoutQrDataUrl (QR of the checkout link, for print or in-person), paymentUri, qrDataUrl, cardEnabled / cardMaxUsd, metadata, createdAt, expiresAt, settlement, and (in on-chain mode) an onchain block with router/token/paymentRef.

Related: GET /api/payments/:id/qr.png returns the checkout QR as a PNG; GET /pay/:id/print is a printable card with the QR and amount, for counters, invoices and events.

GET /api/payments/:id

Fetch current state of a payment (poll as a fallback to webhooks).

GET /api/payments?merchantId=:walletId

List payments for a wallet.

GET /api/config

Public runtime config: active network, chainId, settlement token (symbol/address/decimals), current fees (crypto and card rails), and paymentExpiryMinutes.

Subscriptions (recurring invoices)

A subscription bills a customer on a schedule. On each billing date PayWicket creates that cycle's payment (a normal checkout, payable by card or crypto), emails the customer the pay link, reminds them before the due date, and marks the subscription past_due when an invoice goes unpaid. Because the customer approves every payment from their own wallet or card, no card or key is ever stored by PayWicket or by you.

POST /api/subscriptions

Authenticate with the wallet's x-api-key (or an account token plus walletId).

FieldTypeNotes
namestringRequired. Plan or service name shown on each invoice.
amountUsdnumberRequired. Amount per cycle.
interval, intervalCountstring, numberweek, month or year; count defaults to 1 (e.g. every 3 months).
customerobject{ name, email }; a valid email is required (invoices are sent there).
startAtISO dateFirst billing date. Default: now (first invoice is created immediately unless invoiceNow:false).
dueDaysnumberDays the customer has to pay each invoice (1–60, default 7). The invoice link stays valid until then.
description, metadatastring, objectCopied onto every invoice payment; metadata.subscriptionId and metadata.cycle are added automatically.
GET /api/subscriptions  ·  GET /api/subscriptions/:id

List, or fetch one with its invoices (cycle, status open | paid | overdue | void, dueAt, paymentId and the payment itself).

POST /api/subscriptions/:id/pause · /resume · /cancel · /invoice-now

Pause stops new invoices; resume continues from the next billing date; cancel voids open invoices (paid cycles are untouched); invoice-now issues the next cycle immediately.

Webhooks: subscription.invoice_created, subscription.paid, subscription.past_due, subscription.paused, subscription.active, subscription.canceled, plus the usual payment.confirmed for each invoice. Everything here is also available in the dashboard under Subscriptions.

Payment states

StateMeaningTransition
pendingCreated, awaiting payment.Initial state.
confirmedFunds received on-chain and matched.From pending when the router Paid event (or balance) is observed. Fires payment.confirmed.
expiredNot paid before expiresAt.From pending after the expiry window (default 30 min).
refundedReturned to the payer.From confirmed via refund. Fires payment.refunded.

Webhooks

PayWicket POSTs a JSON event to your callbackUrl (or the wallet's default webhook URL, editable in the dashboard under Wallets) on state changes. Events: payment.confirmed, payment.refunded, and the subscription.* events listed above.

HeaderValue
x-cryptopay-eventThe event type, e.g. payment.confirmed.
x-cryptopay-signatureHMAC-SHA256 hex digest of the raw request body, keyed with the wallet's signing secret (whsec_…, shown in the dashboard under Wallets → Webhook, where you can also rotate it and send a test event).
x-cryptopay-event-idUnique id for the event; the same id is reused on retries, so you can de-duplicate.
x-cryptopay-attempt1, 2 or 3. Failed deliveries (network error or non-2xx) are retried after 30 seconds and again after 5 minutes.

Body:

{
  "id": "evt_9b1c0d2e3f4a5b6c",
  "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
});
Reconcile on the 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:

GET /api/merchants/:id/ledger.csv

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

StatusMeaning
400Invalid request (e.g. bad amountUsd, missing fields).
401Unknown/missing credential (send x-api-key or bearer token). mfa_required when TOTP is needed.
403Not permitted (e.g. refunding a payment you don't own).
404Resource not found.
429Rate 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

Settlement is direct and non-custodial. When a payment confirms, the PaymentRouter smart contract, in a single transaction:

There is no T+N payout delay and no pooled balance — funds arrive at the merchant address as the transaction confirms (seconds on Polygon).

Pricing

Fees are paid by the merchant; the buyer always pays the list price. Each payment carries a fees object (rail, platformFeeUsd, merchantNetUsd) so your reconciliation matches to the cent.

How the buyer paysFeeNotes
Crypto wallet (USDC, gasless)0.5% with a $0.05 minimumEnforced on-chain by the router (max(percentage, floor)). No card network involved.
Card, Apple Pay, Google Pay5.9% + $1.80 per transactionCovers licensed card processing, identity verification and fraud/chargeback liability carried by our payment partner, then converts to USDC. The card leg carries no chargeback risk for you.

Example: a $100 card sale nets the merchant $92.60; a $100 crypto-wallet sale nets $99.50. There are no monthly, setup, or payout fees on either rail.

Refunds & disputes

POST /api/payments/:id/refund

Authenticate with the account bearer token. Only confirmed payments can be refunded, once. Body: toAddress (the customer's wallet; defaults to the original payer for wallet payments), or method: "external" with an optional txHash and note to record a refund you made outside PayWicket (bank, cash, store credit).

Card payments: the payer address on a card payment belongs to the card processor, not the customer, so a refund requires an explicit toAddress the customer gave you, or an external refund record. The dashboard's Refund dialog walks through both.

Crypto transactions are final — there are no bank-style forced chargebacks. A "refund" is a merchant-initiated return of USDC. Disputes are handled off-rail (merchant ↔ customer); PayWicket records the refund tx for your books and fires payment.refunded.

Sandbox & testing

PayWicket runs in three modes so you can integrate before touching real funds:

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

WooCommerce plugin

Download paywicket-woocommerce.zip (v1.1.0). Works with the classic and block checkout and with High-Performance Order Storage.

  1. WordPress admin → Plugins → Add New → Upload Plugin → choose the zip → Activate. WooCommerce must be active.
  2. WooCommerce → Settings → Payments → PayWicket (card or crypto) → Manage.
  3. Enable it, keep the API URL as https://paywicket.com, and paste the wallet API key (sk_…) from your dashboard → Developers → API keys. That key decides which wallet receives the funds.
  4. Place a test order. The customer is sent to the hosted checkout and returned to your order-received page; the order is marked paid once the payment confirms.

To test without real money, use https://sandbox.paywicket.com as the API URL with a key from a sandbox account. Refunds are issued from your PayWicket dashboard; the order flips to refunded on the next callback.

Merchant technical requirements

Because crypto settlement is final and irreversible, your customers should agree to that up front. Add the clause below to your checkout terms (adapt with your own counsel), and link buyers to our Terms & Conditions.

Drop-in checkout clause for your store

Cryptocurrency payments. This site accepts cryptocurrency payments
facilitated by PayWicket, a non-custodial payment technology provider.
If you choose to pay by card, your card payment is processed by an
independent, licensed payment provider (not this store and not
PayWicket), and is subject to that provider's terms and identity-
verification requirements. Once your payment is converted to
cryptocurrency and confirmed on the blockchain, the transaction is
final and cannot be reversed or charged back. Any refund is issued
voluntarily by the merchant in cryptocurrency according to the
merchant's refund policy. By completing a cryptocurrency payment, you
acknowledge and accept these terms and assume the risks associated with
cryptocurrency transactions.
The hosted checkout already shows buyers a short finality notice linking to our Terms, so this is covered on the PayWicket-hosted flow. Add the clause above when you embed payments in your own checkout.

Questions or need a sandbox key? Chat with Wick (our assistant, available 24/7 by chat or by phone at +1 (978) 574-7641), or email support@paywicket.com. Some deeper architecture material (settlement internals, custom routing, commercial terms) is shared under NDA.

ADA Accessibility Compliant · Terms · © 2026 PayWicket