# NxtPay Payments API > Accept payments from your software through NxtPay. Create a hosted checkout link, redirect the customer to it, then confirm the result by polling the order or by receiving a signed webhook. One API key (`npk_…`). All amounts are integer minor units (cents). Base URL: https://payments.nxtpay.cc Auth: `Authorization: Bearer npk_YOUR_KEY` (or header `X-API-Key: npk_YOUR_KEY`) ## Docs - [Full reference (markdown)](https://payments.nxtpay.cc/llms-full.txt): every endpoint, field, example, webhook, and error code — inlined for ingestion. - [OpenAPI 3.1 spec](https://payments.nxtpay.cc/openapi.json): machine-readable; generate a client from it. - [Human docs](https://payments.nxtpay.cc/docs): HTML quickstart with curl/JS/PHP examples. ## Quickstart 1. `POST /v1/payment-links` with `{ amountCents, currency, successUrl, cancelUrl, customer: { email }, environment }` → returns `{ orderId, orderNo, paymentId, paymentUrl, expiresAt, statusToken, routedVia: { whitesiteId } }`. 2. Redirect the customer to `paymentUrl` (a hosted checkout). 3. Confirm the result either way: - Poll: `GET /v1/orders/{orderId}?token={statusToken}&whitesiteId={routedVia.whitesiteId}` → `{ status: "paid" | "awaiting_payment" | "failed" | ... }`. - Or receive a signed webhook (`order.paid`, etc.) at an endpoint you configure in your dashboard. 4. Refund: `POST /v1/refunds` with `{ whitesiteId, paymentId, amountCents }`. ## Endpoints - `POST /v1/payment-links` — create a one-time hosted checkout - `POST /v1/subscriptions` — create a recurring subscription checkout - `POST /v1/subscriptions/cancel` — cancel a subscription (immediate — no more cycles) - `GET /v1/subscriptions/{subscriptionHandle}` — subscription status (pull-based fallback if a webhook is missed) - `GET /v1/orders/{id}?token=&whitesiteId=` — order + payment status - `POST /v1/refunds` — refund a captured payment (full or partial) - `GET /v1/routing-status` — whether NxtPay can route a payment right now - `POST /v1/webhook-endpoint` — self-register your webhook URL (returns your `whsec_…` signing secret — store it) - `POST /v1/heartbeat` — plugin liveness ping (returns `routingAvailable`) ## Key facts for integrators - Test with `environment: "sandbox"` (Stripe test cards like `4242 4242 4242 4242`); go live with `"production"`. - Amounts are integer cents: `2500` = $25.00. Max `amountCents` is `10000000` (100,000.00). - USD only: `currency` must be `USD`; a non-USD request is rejected with `400 unsupported_currency`. - Idempotency: send an `Idempotency-Key` header on `POST /v1/payment-links` and `POST /v1/subscriptions` — a retry with the same key replays the same link/subscription instead of charging again. `partnerReference` is a reference field only (echoed back for reconciliation); it does NOT deduplicate. Refunds are idempotent per `(paymentId, amountCents)`. - On `502 payment_unconfirmed` / `502 subscription_unconfirmed` the charge MAY exist: retry with the SAME `Idempotency-Key` (never a fresh one). The response's `correlationId` is safe to quote to support. - `409 checkout_unrecoverable_use_new_key` is the ONE terminal exception: that idempotency key is permanently unusable (its payment route was removed). Do NOT retry the same key — start a fresh checkout with a NEW `Idempotency-Key`. Every other error is either a clean 4xx or the retry-the-same-key ambiguous case above. - Webhooks: verify `X-Nxtpay-Signature: t=,v1=` = HMAC-SHA256(your `whsec_…` secret, `"{t}.{rawBody}"`); reject if older than ~5 min. - Errors are JSON `{ "error": "machine_code" }` with the right HTTP status (401 `invalid_api_key`, 429 `rate_limited`, 400 validation, 404 not found). - Rate limits (per minute): payment-links & subscriptions 30/key + 5/IP; orders 60/key + 30/IP; refunds 10/key + 5/IP; subscriptions/cancel 20/IP; subscription lookup (GET) 60/IP → 429 with `Retry-After`.