Skip to content
API Documentation

BluPay REST API reference

Every client web service you need to connect the gateway and confirm payments — with authentication, request and response shapes, and the signed payment webhook. For runnable code samples, see the developer overview.

Conventions

Base URL

https://blupay.me/api

Every path below is relative to this base, e.g. POST https://blupay.me/api/v1/invoices/

Authentication

X-API-Key: <your per-shop key>

Create keys per shop in the dashboard. Each key carries scopes (e.g. invoices:write). Optionally restrict a key to specific source IPs (IPv4 or CIDR); leaving the list empty accepts any IP.

Success envelope

{ "error": false, "number": 0, "message": { ... } }

Your data is always under message.

Errors & idempotency

{ "error": true, "number": 404, "message": "Invoice not found." }

number is the HTTP status. Send an Idempotency-Key header on POSTs for safe retries.

Currencies

Discover the assets and networks your shop can accept. Use the returned currencyNetworkId values when restricting an invoice with acceptedCurrencyNetworkIds.

GET/v1/currencies/{type}/currencies:read

List the enabled currencies of a given type.

Path parameters

type*stringEither "crypto" (assets you can be paid in) or "fiat" (display/quote currencies).

Example response

{
  "error": false,
  "number": 0,
  "message": [
    {
      "id": 1,
      "symbol": "USDT",
      "name": "Tether USD",
      "iconUrl": "https://blupay.me/cdn/usdt.png",
      "precision": 6,
      "type": "Crypto",
      "networks": [
        {
          "currencyNetworkId": 3,
          "networkId": 1,
          "networkSymbol": "TRC20",
          "networkName": "Tron",
          "isNative": false,
          "contractAddress": "TR7NHq…",
          "minimumPayment": "1",
          "supportsMemo": false,
          "supportsWalletConnect": true,
          "requiredConfirmations": 19
        }
      ]
    }
  ]
}

Fiat items have the same top-level fields but an empty networks array. Crypto network rows also include minimumDeposit, feePayment, isNative, paymentable, and depositable.

Shops

A shop holds your fee, webhook, and auto-convert settings. Each API key belongs to one shop; you can also manage shops over the API. The plaintext webhook secret is returned only once, when a webhook URL is first set.

GET/v1/shops/shops:read

List the shops owned by the authenticated merchant.

Example response

{
  "error": false,
  "number": 0,
  "message": [
    {
      "id": "c1a2…",
      "uuid": "b3f1c2a4-…",
      "name": "Acme Store",
      "description": null,
      "status": "Active",
      "websiteUrl": "https://acme.com",
      "logoUrl": null,
      "redirectUrl": "https://acme.com/thanks",
      "webhookUrl": "https://acme.com/blupay/webhook",
      "webhookSecret": "********",
      "webhookSecretMasked": "9f2a****",
      "serviceFeeBy": "Merchant",
      "transferFeeBy": "Customer",
      "serviceFeePercent": "1",
      "autoConvertEnabled": false,
      "autoConvertTarget": null,
      "autoConfirmationTolerancePercent": "0",
      "isSandbox": false,
      "posUrl": null,
      "createdAt": "2026-06-09T12:00:00.000Z",
      "updatedAt": "2026-06-09T12:00:00.000Z"
    }
  ]
}
POST/v1/shops/shops:write

Create a new shop.

When you pass a webhookUrl, the response includes a one-time webhookSecretPlain — store it now; it is never shown again. Use it to verify webhook signatures.

Request body

name*string2–120 characters.
descriptionstringUp to 2000 characters.
websiteUrlstring (url)Your storefront URL.
redirectUrlstring (url)Where buyers return after paying.
webhookUrlstring (url)Where BluPay POSTs payment events.
serviceFeeBy"Customer" | "Merchant"Who pays the 1% gateway fee.
transferFeeBy"Customer" | "Merchant"Who pays the on-chain network fee.
autoConvertEnabledbooleanAuto-convert incoming funds.
autoConvertTarget"USDT"Asset to auto-convert into.
autoConfirmationTolerancePercentnumber0–3. Treat tiny underpayments as paid.
isSandboxbooleanCreate a sandbox (test) shop.

Example request

{
  "name": "Acme Store",
  "websiteUrl": "https://acme.com",
  "webhookUrl": "https://acme.com/blupay/webhook",
  "serviceFeeBy": "Merchant",
  "transferFeeBy": "Customer"
}

Example response

{
  "error": false,
  "number": 0,
  "message": {
    "uuid": "b3f1c2a4-…",
    "name": "Acme Store",
    "webhookUrl": "https://acme.com/blupay/webhook",
    "webhookSecretPlain": "whsec_b1f4…  ← shown once",
    "isSandbox": false,
    "status": "Active"
  }
}
GET/v1/shops/{shopId}/shops:read

Retrieve a single shop by its uuid.

Path parameters

shopId*string (uuid)The shop uuid.

Example response

{ "error": false, "number": 0, "message": {
    "id": "c1a2…",
    "uuid": "b3f1c2a4-…",
    "name": "Acme Store",
    "description": null,
    "status": "Active",
    "websiteUrl": "https://acme.com",
    "logoUrl": null,
    "redirectUrl": "https://acme.com/thanks",
    "webhookUrl": "https://acme.com/blupay/webhook",
    "webhookSecret": "********",
    "webhookSecretMasked": "9f2a****",
    "serviceFeeBy": "Merchant",
    "transferFeeBy": "Customer",
    "serviceFeePercent": "1",
    "autoConvertEnabled": false,
    "autoConvertTarget": null,
    "autoConfirmationTolerancePercent": "0",
    "isSandbox": false,
    "posUrl": null,
    "createdAt": "2026-06-09T12:00:00.000Z",
    "updatedAt": "2026-06-09T12:00:00.000Z"
  } }
PUT/v1/shops/{shopId}/shops:write

Update a shop. All fields are optional (send only what changes).

Setting a new webhookUrl rotates the secret and returns a fresh one-time webhookSecretPlain.

Path parameters

shopId*string (uuid)The shop uuid.

Request body

partialAny field from “Create a new shop”, all optional.

Example request

{ "serviceFeeBy": "Customer", "transferFeeBy": "Customer" }

Example response

{ "error": false, "number": 0, "message": { "uuid": "b3f1c2a4-…", "serviceFeeBy": "Customer" } }

Invoices

The core of the integration. Create an invoice, redirect the buyer to its paymentUrl, then confirm the result via webhook and/or by polling the invoice.

POST/v1/invoices/invoices:write

Create an invoice (the gateway charge). Returns 201.

You quote in fiat (or any currencySymbol); the crypto amount is locked at creation for the payment window. Redirect the buyer to the returned paymentUrl. Send an Idempotency-Key header so retries never create a duplicate.

Request body

currencyAmount*stringPositive decimal as a string, e.g. "49.00".
currencySymbol*stringCurrency the amount is quoted in, e.g. "USD".
shopIdstring (uuid)Target shop. Defaults to the API key’s shop.
orderIdstringYour order reference (≤120 chars).
orderDescriptionstringShown on the checkout (≤2000 chars).
customerEmailstring (email)Buyer email for receipts.
redirectUrlstring (url)Where the buyer returns after paying.
webhookUrlstring (url)Override the shop webhook for this invoice.
timeForPaymentnumberMinutes until expiry (5–180).
rateMode"Fixed" | "Dynamic"Lock the rate or track it live.
serviceFeeBy"Customer" | "Merchant"Override who pays the gateway fee.
transferFeeBy"Customer" | "Merchant"Override who pays the network fee.
acceptedCurrencyNetworkIdsnumber[]Restrict payable assets to these currencyNetworkId values.
isSandboxbooleanCreate a sandbox invoice.

Example request

{
  "shopId": "b3f1c2a4-…",
  "currencyAmount": "49.00",
  "currencySymbol": "USD",
  "orderId": "order-1024",
  "orderDescription": "Pro plan — monthly",
  "customerEmail": "buyer@example.com",
  "redirectUrl": "https://acme.com/thanks",
  "webhookUrl": "https://acme.com/blupay/webhook",
  "timeForPayment": 30,
  "acceptedCurrencyNetworkIds": [3, 7]
}

Example response

{ "error": false, "number": 0, "message": {
    "link": "inv_8f3c9a",
    "status": "Created",
    "source": "API",
    "currencyAmount": "49.00",
    "currencySymbol": "USD",
    "remainingCurrencyAmount": "49.00",
    "customerEmail": "buyer@example.com",
    "orderId": "order-1024",
    "orderDescription": "Pro plan — monthly",
    "expiresAt": "2026-06-09T12:30:00.000Z",
    "serviceFeeBy": "Merchant",
    "transferFeeBy": "Customer",
    "serviceFeePercent": "1",
    "autoConvertEnabled": false,
    "autoConvertTarget": null,
    "isSandbox": false,
    "paymentUrl": "https://blupay.me/pay/inv_8f3c9a",
    "shop": { "uuid": "b3f1c2a4-…", "name": "Acme", "logoUrl": null, "websiteUrl": "https://acme.com" },
    "createdAt": "2026-06-09T12:00:00.000Z"
  } }
GET/v1/invoices/invoices:read

List invoices with filters and pagination.

Query parameters

statusstringCreated · Pending · Paid · Partially · PartiallyExpired · Expired · Canceled.
shopIdstring (uuid)Filter by shop.
fromstring (ISO date)Created on/after this time.
tostring (ISO date)Created on/before this time.
searchstringMatch link, orderId, or customerEmail.
sourcestringDashboard · API · WooCommerce · POS · PaymentButton · PaymentLink.
pagenumberDefaults to 1.
pageSizenumberDefaults to 20 (max 200).

Example response

{
  "error": false,
  "number": 0,
  "message": {
    "items": [ { /* invoice object */ } ],
    "total": 137,
    "page": 1,
    "pageSize": 20
  }
}
GET/v1/invoices/{link}/invoices:read

Retrieve a single invoice and poll its status to confirm payment.

Path parameters

link*stringThe invoice link returned at creation.

Example response

{ "error": false, "number": 0, "message": {
    "link": "inv_8f3c9a",
    "status": "Created",
    "source": "API",
    "currencyAmount": "49.00",
    "currencySymbol": "USD",
    "remainingCurrencyAmount": "49.00",
    "customerEmail": "buyer@example.com",
    "orderId": "order-1024",
    "orderDescription": "Pro plan — monthly",
    "expiresAt": "2026-06-09T12:30:00.000Z",
    "serviceFeeBy": "Merchant",
    "transferFeeBy": "Customer",
    "serviceFeePercent": "1",
    "autoConvertEnabled": false,
    "autoConvertTarget": null,
    "isSandbox": false,
    "paymentUrl": "https://blupay.me/pay/inv_8f3c9a",
    "shop": { "uuid": "b3f1c2a4-…", "name": "Acme", "logoUrl": null, "websiteUrl": "https://acme.com" },
    "createdAt": "2026-06-09T12:00:00.000Z"
  } }
GET/v1/invoices/{link}/pdf/invoices:read

Download a PDF copy of the invoice.

Path parameters

link*stringThe invoice link.

Example response

200 OK
Content-Type: application/pdf
(binary PDF stream)
GET/v1/invoices/export/csv/invoices:read

Stream all of the merchant’s invoices as CSV.

Example response

200 OK
Content-Type: text/csv; charset=utf-8
Content-Disposition: attachment; filename="invoices.csv"

Confirming payments & fulfilling orders

Crypto payments are confirmed server-to-server, never in the browser. Treat the buyer returning to your site as a UX convenience only — always confirm on your backend before you release an order.

Recommended

1 · Listen for the webhook

BluPay POSTs a signed invoice.paid event to your webhookUrl as soon as the invoice is fully settled. Verify the signature, mark the order paid once (keyed on event_id), then fulfill. See Payment webhook.

Fallback

2 · Poll the invoice

From your server, call GET /v1/invoices/{link}/ and release the order when status === "Paid". Handy as a backstop, on your thank-you page, or for reconciliation.

Redirect (return) URL — what is sent

Set redirectUrl on the shop, or per invoice when creating it. After the buyer finishes, the hosted checkout shows a “Return to merchant” button that links to that URL. BluPay does not append any status, invoice id, or signature to it, and the buyer may close the tab before clicking — so never fulfill an order based on the redirect alone. Use it only to bring the customer back into your flow.

Is there a “verify payment” call like card / bank gateways?

No — and you don’t need one. Card and bank PSPs use a second verify / capture round-trip because the browser redirect is their only channel and it can’t be trusted. Crypto gateways instead authenticate the result with a cryptographically signed webhook, so a valid X-BluPay-Signature (or a server-side status fetch) is the verification. This is the same model used by Coinbase Commerce, BTCPay Server, and NOWPayments.

Payment confirmation webhook

On every invoice state change, BluPay sends a signed POST to the invoice’s (or shop’s) webhookUrl. This is how you confirm a payment server-to-server. Verify the signature, enforce a 5-minute replay window, then respond 200. Failed deliveries retry with exponential backoff.

Request headers

X-BluPay-Event-IdUnique event id (e.g. evt_…).
X-BluPay-TimestampUnix time in milliseconds (string).
X-BluPay-SignatureHMAC-SHA256, 64-char lowercase hex.

event_type values

invoice.created · invoice.pending · invoice.paid · invoice.partially_paid · invoice.partially_expired · invoice.expired · invoice.canceled

invoice.paid means the invoice is fully settled.

Example payload

{
  "event_id": "evt_3kf9…",
  "event_type": "invoice.paid",
  "created_at": "2026-06-09T12:10:33.000Z",
  "data": {
    "link": "inv_8f3c9a",
    "status": "Paid",
    "currencyAmount": "49.00",
    "currencySymbol": "USD",
    "remainingCurrencyAmount": "0"
  }
}

Verify the signature (Node.js)

import crypto from 'node:crypto';

app.post('/blupay/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const secret = process.env.BLUPAY_WEBHOOK_SECRET;   // shop's webhookSecretPlain
  const ts  = req.header('X-BluPay-Timestamp');
  const sig = req.header('X-BluPay-Signature');

  if (Math.abs(Date.now() - Number(ts)) > 5 * 60 * 1000) return res.status(400).end('stale');

  const expected = crypto.createHmac('sha256', secret)
    .update(ts + req.body.toString('utf8'))           // timestamp + raw body
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)))
    return res.status(400).end('bad-signature');

  const event = JSON.parse(req.body);
  // event.event_type: invoice.paid | invoice.expired | ...
  res.status(200).end('ok');
});

Need a key or stuck on something? Contact support — we usually reply within one business day.