Private beta. MaxPay is not open to outside merchants yet, and crypto payments carry risk. How every payment is screened

For developers

Developer guide

Accept crypto with one API call and one webhook. All amounts are integers in the currency's minor units (cents for USD).

Keys come from the MaxPay team

In the private beta, the MaxPay team opens your store account, sets its webhook URL, and gives you its API key and webhook signing secret. Sign in to follow payments, balances and withdrawals.

Sign in

1. Create a payment request

Use your store API key, which the MaxPay team issues with your store account. orderReference is your order id. Sending the same request again returns the same payment request, so retries are safe.

curl https://<your-maxpay-host>/v1/payment-requests \
  -H "Authorization: Bearer $MAXPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "orderReference": "order-1001",
    "amount": { "amountMinor": 2500, "currency": "USD" },
    "description": "Order #1001",
    "returnUrl": "https://shop.example/thanks"
  }'

The response includes the hosted checkout URL and the widget token:

{
  "id": "pr_01J…",
  "orderReference": "order-1001",
  "status": "OPEN",
  "amount": { "amountMinor": 2500, "currency": "USD" },
  "checkoutUrl": "https://<your-maxpay-host>/c/…",
  "widgetToken": "…",
  "expiresAt": "…"
}

2. Send the shopper to checkout

Redirect to checkoutUrl, or embed the widget on your own page:

<div id="maxpay"></div>
<script src="https://<your-maxpay-host>/embed.v1.js"></script>
<script>MaxPay.mount('#maxpay', { token: WIDGET_TOKEN });</script>

Never mark an order paid because the shopper came back to returnUrl or because the widget said so. Wait for the signed webhook.

3. Handle signed webhooks

MaxPay POSTs a JSON event to your webhook URL with the header MaxPay-Signature. Verify it on the raw body before trusting it. Events carry a sequence per order: ignore one older than the last you applied. Each event has an id: apply it once.

import { createHmac, timingSafeEqual } from 'node:crypto';

// header: "t=<unix seconds>,v1=<hex>" (two v1 values while a secret is rotated)
export function verify(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = header.split(',').map((p) => p.trim());
  const t = Number(parts.find((p) => p.startsWith('t='))?.slice(2));
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
  return parts
    .filter((p) => p.startsWith('v1='))
    .some((p) => {
      const got = Buffer.from(p.slice(3), 'hex');
      return got.length === expected.length && timingSafeEqual(got, expected);
    });
}
Event types
TypeMeaning
payment_request.payment_seenA payment was seen on chain and is waiting for confirmations.
payment_request.underpaidPart of the amount arrived. The order stays open for the rest.
payment_request.paidThe order is paid in full. Fulfil it.
payment_request.overpaidMore than the amount arrived. Choose to keep it as credit or refund it.
payment_request.heldA payment was flagged by screening and is under review.
payment_request.releasedA held payment was cleared after review.
payment_request.rejected_paymentA held payment was rejected after review.
payment_request.late_paymentA payment arrived after the window closed. Review it in the dashboard.
payment_request.late_declinedYou declined a late payment.
payment_request.expiredThe order expired unpaid.
payment_request.cancelledThe order was cancelled.

Respond with a 2xx status within 10 seconds. Failed deliveries are retried after 10 s, 30 s, 2 min, 10 min, 30 min, then hourly.

WooCommerce

  1. Copy the maxpay-woocommerce plugin into wp-content/plugins/ and activate it.
  2. In WooCommerce → Settings → Payments → MaxPay, enter the API URL, your store API key and the webhook signing secret.
  3. Ask the MaxPay team to set your store’s webhook URL to https://your-shop/wp-json/maxpay/v1/webhook.

Orders move to processing on paid, go on hold when underpaid, held or late, and are cancelled when an unpaid request expires.

More

  • Payment links, invoices and subscriptions: /v1/payment-links, /v1/invoices, /v1/subscriptions, or the Billing page in the dashboard.
  • Balances and withdrawals: /v1/balances, /v1/withdrawals. Withdrawals need approval from several MaxPay operators.
  • Exports: /v1/exports/accounting.xlsx and /v1/exports/maxaccounting.json for a date range.