Documentation

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.

API base URL
https://normiepay.xyz

Two ways to integrate

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.

WooCommerce plugin

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.

  1. Download the plugin and upload it under Plugins → Add New → Upload Plugin.
  2. Create a Live API key in Dashboard → API Keys.
  3. Open WooCommerce → Settings → Payments → NormiePay, paste the key, and enable it — the webhook registers itself.

Works with both block and classic checkout. Full guide: /woocommerce. (Also pending publication on the WordPress.org directory.)

How settlement works (non-custodial)

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.

Quickstart (API)

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.

Node SDK

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);

Webhooks

Configure your endpoint in Dashboard → Webhooks and copy the signing secret (whsec_…). NormiePay POSTs events with these headers:

  • X-Signature — hex HMAC signature
  • X-Timestamp — unix seconds
  • X-Event-Id — unique id (dedupe on this)

Signing algorithm

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).

Event types & payload

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.

Supported chains

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.

Paid Telegram & Discord memberships

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).

Telegram

  1. Create your paid private channel — the one members join after paying, not your free/promo channel — and add @NormiePayBot as an admin of it.
  2. The bot posts that channel's chat ID. In Dashboard → Telegram, create a membership with that ID, a price, and a duration. That chat ID is the paid channel the bot guards.
  3. Share the join link (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.

Discord

  1. Add the NormiePay bot to your server (grant Manage Roles) and create the paid role, with the bot's role ranked above it.
  2. In Dashboard → Discord, create a membership with your Server ID + Role ID, a price, and a duration.
  3. Share the join link. Members sign in with Discord, pay USDC, and the bot assigns the role instantly — then strips it when access ends (renewals re-add it).

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.

Test mode

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_.

Need help? Reach out to support. · Home