CubePay docs

Accept crypto payments on Cube Chain

CubePay is a hosted payment gateway: you create an invoice over HTTPS, send the buyer to the returned checkout page, and get a signed webhook when the payment confirms on-chain. Flat 1% — no chargebacks, no card networks.

Introduction

Everything runs against a single base URL. All request and response bodies are JSON.

Base URL

https://cubepay-backend.vercel.app

The flow: create an invoice server-side → redirect the buyer to checkout_url → the buyer pays on Cube Chain → you receive invoice.paid on your webhook → funds are swept to your payout address (invoice.completed).

Authentication

Authenticate every API call with your secret key in the Authorization header:

header
Authorization: Bearer ck_live_…

Your key is shown once at signup and can be rotated from the dashboard (Settings → API keys). It is stored hashed on our side and can never be retrieved — keep it in an environment variable on your server and never ship it to a browser or mobile bundle. The dashboard itself signs in with email + password, which issues a 24-hour session token with the same permissions.

Currencies & settlement tokens

Invoices are priced in fiat (price_currency) and the pricing currency pins the on-chain token — there is nothing for the buyer to choose:

USDUSDTUSD-priced invoices are paid and settled only in USDT
NGNxNGNNGN-priced invoices are paid and settled only in xNGN

Passing a mismatched pay_token (e.g. USDT on an NGN invoice) returns 422. The quote rate is locked when the invoice is created and the exact token amount appears inpay_amount.

One account can also run the two currencies separately: the dashboard (Settings → Currency profiles) issues an NGN-scoped and a USD-scoped API key, each with its own webhook URL and settlement wallet. A scoped key defaults to its currency — price_currency can be omitted — and may not create invoices in the other one.

Create an invoice

One call creates the invoice, derives a unique deposit address, locks the quote and returnscheckout_url. Pick your stack:

cURL
curl -X POST https://cubepay-backend.vercel.app/api/v1/invoices \
  -H "Authorization: Bearer ck_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "price_amount": "25000",
    "price_currency": "NGN",
    "order_id": "order_1042",
    "description": "Cube Phone case",
    "success_url": "https://store.com/thank-you"
  }'
# → { "id": "inv_…", "checkout_url": "https://cubepay-backend.vercel.app/checkout/inv_…", … }

Optional fields: ttl_minutes (payment window, 5 min – 7 days, default 15),metadata (arbitrary JSON echoed back in webhooks), order_id,description, success_url and cancel_url.

Hosted checkout & drop-in SDK

checkout_url serves a mobile-friendly hosted page with the amount, a QR code and live payment status — nothing to build. To keep buyers on your site, load the drop-in SDK and open the checkout in a modal:

HTML
<script src="https://cubepay-backend.vercel.app/sdk/cubepay.js"></script>
<script>
  CubePay.open({
    checkoutUrl: invoice.checkout_url,
    onPaid: (invoiceId) => { window.location = "/thank-you"; },
  });
</script>

The checkout's “paid” screen is buyer UX only — fulfil orders exclusively on the signedinvoice.paid webhook.

Webhooks

Set your endpoint in the dashboard (Settings → Integration). Every delivery is an HTTP POST with these headers; failed deliveries retry with backoff (30s → 6h, 8 attempts). Respond 2xx within 10 seconds and process each X-CubePay-Delivery id once.

headers
X-CubePay-Event:     invoice.paid
X-CubePay-Delivery:  evt_…            # idempotency key — process each once
X-CubePay-Timestamp: 1760000000
X-CubePay-Signature: hex(HMAC_SHA256(webhook_secret, "{timestamp}." + raw_body))
invoice.pendingPayment seen on-chain, awaiting confirmations
invoice.paidConfirmed in full — fulfil the order on this event
invoice.underpaidConfirmed amount below the quote (outside tolerance)
invoice.expiredTTL elapsed with no payment
invoice.completedFunds swept to your payout address

Verify the signature with your webhook secret (Settings → API keys → Reveal) before trusting a delivery:

Node.js
import crypto from "crypto";

function verifyCubePay(req, rawBody) {
  const ts = req.headers["x-cubepay-timestamp"];
  const sig = req.headers["x-cubepay-signature"];
  const expected = crypto.createHmac("sha256", process.env.CUBEPAY_WEBHOOK_SECRET)
    .update(`${ts}.`).update(rawBody).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}
// process each X-CubePay-Delivery id once; respond 2xx within 10s

API reference

POST/api/v1/auth/loginEmail + password → 24h session token
GET/api/v1/merchants/meYour merchant profile
PATCH/api/v1/merchants/meUpdate webhook URL / payout address
POST/api/v1/merchants/me/rotate-keyRotate API key (shown once)
POST/api/v1/merchants/me/rotate-webhook-secretRotate webhook secret
POST/api/v1/merchants/me/reveal-webhook-secretReveal the current webhook secret
GET/api/v1/merchants/me/currency-profilesPer-currency keys, webhooks & settlement wallets
POST/api/v1/merchants/me/currency-profiles/{ccy}/rotate-keyCreate/rotate the NGN- or USD-scoped key
PATCH/api/v1/merchants/me/currency-profiles/{ccy}Set per-currency webhook URL / settlement wallet
POST/api/v1/invoicesCreate invoice (price_amount, price_currency, ttl_minutes?, metadata?)
GET/api/v1/invoices · /{id}List / fetch invoices
POST/api/v1/invoices/{id}/settleSweep a paid invoice to your payout address
POST/api/v1/payment-linksCreate a reusable payment link
GET/api/v1/payment-linksList links with visit & paid stats
PATCH/api/v1/payment-links/{id}Pause / activate a link
GET/l/{id}Public: spawns a fresh invoice and redirects to checkout
GET/checkout/{id}Public: hosted checkout page

Errors

Errors return a JSON body with a human-readable detail message:

response
{ "detail": "NGN invoices are payable only in XNGN" }
401Missing, invalid or expired credentials
404Resource not found (or owned by another merchant)
409Conflict — e.g. settling an unpaid invoice
422Validation failed — the detail message says which field
5xxGateway problem — retry with backoff

Questions or stuck on an integration? Reach out from your dashboard — or open the Developers tab for keys and live examples.