n4n.pay
Sign in
Integration

Accept a payment in four steps

Create an intent from your server, send the customer to the hosted checkout (or embed it), and confirm the result with a signed webhook. All amounts are integer micro-USD — 1000000 = $1.00.

1 · Create a payment intent

From your backend, authenticate with your secret key (n4npay_sk_…) — created on your app's page in the dashboard, and scoped to that app. POST the amount; success_url and cancel_url are optional — omit them and we fall back to the app's default callback URLs (also set on the app's page), or pass explicit ones to override per intent. The response is the created intent, tagged with your app_id, plus a checkout_url and a one-time client_secret.

curl · create intent
curl https://pay.n4n.io/v1/payment_intents \
  -H "Authorization: Bearer n4npay_sk_3f9a2b7c8d1e4f5a6b0c9d8e7f2a1b3c9e4f5a6b0c8d7e2f1a3b9c8d6e4f5a2b" \
  -H "Content-Type: application/json" \
  -d '{
    "amount_microusd": 4990000,
    "description": "Pro plan",
    "success_url": "https://shop.example/thanks",
    "cancel_url": "https://shop.example/cart"
  }'

# → 201 Created
# {
#   "id": "9b2f6d4e-1c3a-4e8f-9a21-7c5d2e8b4f10",
#   "object": "payment_intent",
#   "app_id": "3caa9e6b-9b0e-4b7d-9c9b-2b4b6e7d8f10",
#   "amount_microusd": 4990000,
#   "currency": "USD",
#   "description": "Pro plan",
#   "metadata": {},
#   "status": "requires_payment",
#   "success_url": "https://shop.example/thanks",
#   "cancel_url": "https://shop.example/cart",
#   "expires_at": "2026-07-10T12:34:56Z",
#   "created_at": "2026-07-09T12:34:56Z",
#   "client_secret": "cs_…",
#   "checkout_url": "https://pay.n4n.io/checkout/9b2f6d4e-1c3a-4e8f-9a21-7c5d2e8b4f10?cs=cs_…"
# }

2 · Send the customer to checkout

The simplest integration: redirect the browser to the checkout_url from the response. We host the payment UI, offering whichever methods are enabled for your app — PayPal and/or self-custody crypto, configured on your app's page; after payment we redirect to your success_url.

server · redirect
// after creating the intent server-side:
res.redirect(intent.checkout_url);

3 · Or embed the checkout inline

Drop embed.js onto your page and mount the checkout in a container. The widget appends an iframe pointing at the checkout_url and calls onSuccess when the payment settles. Your page's origin must be listed in your app's allowed origins.

html · embed snippet
<script src="https://pay.n4n.io/embed.js"></script>
<div id="pay-here"></div>
<script>
  N4nPay.embed({
    checkoutUrl: intent.checkout_url, // from your server
    container: "#pay-here",
    onSuccess: function (p) { window.location = "/thanks?pi=" + p.intentId; }
  });
</script>

embed({checkoutUrl, container, onSuccess}) returns {destroy()} to remove the iframe and its listener. Completion is delivered over postMessage as {type:"n4npay", event:"succeeded", intent_id} from the pay.n4n.io origin — the widget validates the origin before invoking your callback.

4 · Verify webhooks

Payments are confirmed asynchronously. Each delivery carries an N4nPay-Signature header — verify it with your endpoint's signing secret (whsec_…) before trusting the body. The signed payload is <t> + "." + rawBody; compare in constant time and reject stale timestamps.

header · N4nPay-Signature
N4nPay-Signature: t=1700000000,v1=hex(hmac_sha256(secret, t + "." + body))
pseudo-code · verify
func verify(secret, header, rawBody, now) bool {
    // header: "t=<unix>,v1=<hex>"
    t, v1 := parse(header)                       // split on "," then "="
    if abs(now - t) > tolerance { return false } // reject replays (e.g. 5m)
    expected := hex(hmac_sha256(secret, t + "." + rawBody))
    return constant_time_equal(v1, expected)
}

Each app configures its own webhook endpoints (and its own signing secret) on the app's page in the dashboard — a shared handler across apps can tell intents apart via data.payment_intent.app_id.

json · event body
{
  "type": "payment_intent.succeeded",
  "created_at": "2026-07-09T12:35:04Z",
  "data": {
    "payment_intent": {
      "id": "9b2f6d4e-1c3a-4e8f-9a21-7c5d2e8b4f10",
      "object": "payment_intent",
      "app_id": "3caa9e6b-9b0e-4b7d-9c9b-2b4b6e7d8f10",
      "amount_microusd": 4990000,
      "currency": "USD",
      "description": "Pro plan",
      "metadata": {},
      "status": "succeeded",
      "success_url": "https://shop.example/thanks",
      "cancel_url": "https://shop.example/cart",
      "expires_at": "2026-07-10T12:34:56Z",
      "created_at": "2026-07-09T12:34:56Z"
    }
  }
}

Event catalogue

Every event body carries type, created_at (RFC3339), and a data.payment_intent object — the same shape returned by the API, app_id included.

payment_intent.succeeded Payment captured (PayPal) or confirmed on-chain — fulfil the order.
payment_intent.expired The intent's TTL elapsed with no settled payment.
payment_intent.canceled You canceled the intent via the API before it settled.