Accept USDC on Base & Polygon from any wallet — non-custodially. Every payment settles through an on-chain smart contract straight to your own wallet; NormiePay never holds your funds. Your stack doesn't matter — you only ever touch one API call (or a link) and one webhook.
1. No code — create a Payment Link in the dashboard and share the URL, or paste the Buy Button snippet into any website. Nothing to build.
2. API — create a checkout session from your backend, redirect the customer, and receive a signed webhook when the payment confirms. Use this when the amount or customer varies.
Before creating links or sessions, add your company name and a Base payout wallet in Dashboard → Settings.
Running a WooCommerce store? Install the NormiePay Crypto Checkout plugin to accept USDC with no code. Customers pay from any wallet on Base or Polygon and orders are marked paid automatically on-chain.
Works with both block and classic checkout. Full guide: /woocommerce. (Also pending publication on the WordPress.org directory.)
NormiePay is non-custodial. When you create a checkout, we derive a unique deposit address that is a deterministic smart contract, committed in advance to a fixed split: your wallet, our 2% fee, and the exact amount due.
When the customer pays, that contract settles on-chain in a single transaction — 98% to your wallet, 2% to NormiePay. The funds never pass through a NormiePay-controlled account, and the contract cannot pay anyone other than you and our fee wallet. We can never hold, freeze, or redirect your money.
Over- and underpayments are handled automatically: an overpayment settles the correct amount and refunds the excess to the payer on-chain; an underpayment is refunded in full. In both cases the customer gets an email with the transaction link.
Create a checkout session and redirect the customer to checkout_url.
curl -X POST https://normiepay.xyz/api/v1/checkout/sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": "49.00",
"customer_email": "user@example.com",
"success_url": "https://yourapp.com/success",
"metadata": { "user_id": "u_123", "plan": "pro" }
}'Response:
{
"id": "cs_...",
"checkout_url": "https://normiepay.xyz/checkout/cs_...",
"status": "pending",
"amount_due": "49.00",
"asset": "USDC",
"chain": "base",
"deposit_address": "0x...",
"expires_at": "..."
}Sessions are non-custodial by default — the deposit_address is the on-chain splitter contract. Send the customer to checkout_url; they pick Base or Polygon and pay USDC from any wallet.
A zero-dependency client (sdk/node/normiepay.mjs):
import { NormiePay } from "normiepay";
const np = new NormiePay(process.env.NORMIEPAY_API_KEY);
const session = await np.createCheckoutSession({
amount: "49.00",
metadata: { user_id: "u_123" },
});
res.redirect(session.checkout_url);Configure your endpoint in Dashboard → Webhooks and copy the signing secret (whsec_…). NormiePay POSTs events with these headers:
X-Signature — hex HMAC signatureX-Timestamp — unix secondsX-Event-Id — unique id (dedupe on this)The signature is HMAC-SHA256, hex-encoded, computed over the string `${X-Timestamp}.${rawRequestBody}`using your endpoint's signing secret as the key. Verify with a constant-time comparison against X-Signature, and reject timestamps older than 5 minutes. Use the raw, unparsed body.
import crypto from "crypto";
function verify(rawBody, headers, secret) {
const ts = headers["x-timestamp"];
const sig = headers["x-signature"];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // 5 min
const expected = crypto.createHmac("sha256", secret)
.update(`${ts}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}Or use the SDK: NormiePay.verifyWebhook(rawBody, headers, secret).
payment.confirmed, payment.failed, checkout.expired
Subscription payment links also emit subscription.active (paid or renewed), subscription.reminder (before expiry), and subscription.expired (lapsed). Grant access on subscription.active and revoke it on subscription.expired — both carry subscription_id, customer_email, and expires_at. NormiePay also emails the customer renewal reminders (72h / 24h / 2h before expiry) automatically.
{
"id": "evt_...",
"type": "payment.confirmed",
"created_at": "2026-07-02T09:32:49.874Z",
"data": {
"checkout_session_id": "cs_...",
"amount": "49.000000",
"asset": "USDC",
"chain": "base",
"tx_hash": "0x...",
"customer_email": "user@example.com",
"metadata": { "user_id": "u_123", "plan": "pro" }
}
}Grant access on payment.confirmed, keyed by data.metadata.
Non-custodial settlement runs on USDC on Base and Polygon — the customer picks which one at checkout. More chains coming as the splitter contract is deployed to them. Add a single EVM payout wallet (a 0x… address) in Settings — it covers both chains.
Beyond one-off payments, NormiePay can run recurring paid communitieson Telegram and Discord with no code. Members pay USDC for timed access; the bot grants it on payment, reminds them before it lapses, and removes them if they don't renew. Settlement is the same non-custodial split straight to your wallet. Membership payments carry a flat 3.5% fee (no monthly cost).
t.me/NormiePayBot?start=…) anywhere — your free channel, bio, or site. A buyer taps it, pays in a DM with the bot, and gets a single-use invite to the paid channel; lapsed members are removed automatically.Members receive reminders (72h / 24h / 2h before expiry) by direct message and email. Access grants are triggered by the same payment.confirmed event — no webhook wiring needed on your end.
Create a TEST API key or a TEST payment link to build against the full flow without real funds. Test checkouts settle on the Base Sepolia testnet — only ever send testnet USDC to a test checkout; real funds sent to a test address are permanently lost. Live keys start with pk_live_, test keys with pk_test_.