Developers

PoweX Pay API

Accept crypto payments on your site, app or chat. Create an invoice or a payment link, send the customer to a hosted checkout, and get a signed webhook the moment the money lands — credited, minus the fee, straight into your PoweX wallet.

How it works

  1. Create an invoice (or share a payment link) with your server and your API key.
  2. Send the customer to checkoutUrl. They pick a coin and network, scan the QR code and pay; the price in that coin is locked for a few minutes.
  3. We watch the chain. When the payment is confirmed it is credited to you and your webhook fires: invoice.payment_received, then invoice.paid.
  4. You fulfil the order. The net amount is already in your PoweX wallet, in the same coin the customer paid with. Swap it or withdraw it whenever you like.

Amounts are priced in USD. Customers pay in the coin they choose; you are never exposed to a price move after the checkout locks the quote.

Quickstart

1. Enable PoweX Pay in the dashboard (new here? Create a merchant account) and create a test key (pxk_test_…). Test mode moves no money and never touches the blockchain.

2. Create an invoice:

curl https://powex.me/v1/invoices \
  -H "Authorization: Bearer pxk_test_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042" \
  -d '{
    "amount": "25.00",
    "reference": "order-1042",
    "description": "T-shirt",
    "returnUrl": "https://shop.example.com/thanks"
  }'

Response (201):

JSON
{
  "id": "inv_Qm8x2VtFhKz0aBcDeFgHiJ",
  "object": "invoice",
  "livemode": false,
  "status": "OPEN",
  "amount": "25",
  "currency": "USD",
  "reference": "order-1042",
  "description": "T-shirt",
  "feeBps": 50,
  "checkoutUrl": "https://powex.me/i/inv_Qm8x2VtFhKz0aBcDeFgHiJ",
  "expiresAt": "2026-10-21T17:20:00.000Z",
  "paidUsd": "0",
  "remainingUsd": "25",
  "payment": null,
  "payments": []
}

3. Open checkoutUrl to see the customer's checkout, then pretend they paid:

curl -X POST https://powex.me/v1/invoices/inv_…/simulate \
  -H "Authorization: Bearer pxk_test_…" \
  -H "Content-Type: application/json" \
  -d '{ "outcome": "paid" }'

The invoice becomes PAID, a payment appears in your statement, and — if you registered a webhook — your endpoint receives the events.

Authentication

Send your key as Authorization: Bearer pxk_… (or X-Api-Key: pxk_…). Keys are created in the dashboard, shown once, and can be revoked at any time. Keep them on your server — never in a browser or mobile app.

A key is either test (pxk_test_…) or live (pxk_live_…); the mode comes from the key, never from the request. Live keys need an approved merchant account. Each key also carries scopes — give an integration only what it needs:

ScopeAllows
invoices:readRead invoices, payments, summary and payment links.
invoices:writeCreate and cancel invoices; simulate outcomes in test mode.
links:writeCreate, update and delete payment links.
webhooks:manageCreate, change, rotate and test webhook endpoints; read the delivery log.
payouts:readRead payouts and your wallet balance.
payouts:writeSend payouts from your wallet. Moves money: give it to as few keys as possible.
addresses:readRead your customers' fixed deposit addresses and the deposits they receive.
addresses:writeCreate fixed deposit addresses for your customers.
refunds:writeRefund balance payments back to the customer, from your wallet.
customers:readRead your customers (name, email, phone, masked tax id).
customers:writeCreate and edit customers. Personal data: give it only to the systems that need it.
subscriptions:readRead subscription plans and subscriptions.
subscriptions:writeCreate and archive plans, cancel subscriptions; simulate subscriptions in test mode.
accounts:readPlatforms: list and read your subaccounts.
accounts:writePlatforms: create subaccounts and edit them.
accounts:actPlatforms: act on behalf of a subaccount with the PoweX-Account header (each call still needs its own scope).
transfers:readPlatforms: list and read transfers between your wallet and your subaccounts.
transfers:writePlatforms: move money between your wallet and your subaccounts. Moves money: give it to as few keys as possible.

Lock a key to your servers. In the dashboard, give each key the IPs or ranges it may be used from (203.0.113.7, 198.51.100.0/24, IPv6 too; up to 20). A leaked key then does not work anywhere else — the call is refused with API_KEY_IP_NOT_ALLOWED. Changing the list needs your PIN and takes effect on the next request. Strongly recommended for live keys with payouts:write or transfers:write.

GET /v1/me returns the merchant, the key's mode and its scopes — handy as a connection test.

Conventions

  • Base URL: https://powex.me — JSON in, JSON out, TLS only.
  • Amounts are strings in canonical decimal form ("25", "10.5", "0.125"). Never parse money with floating point; use a decimal type.
  • Dates are ISO 8601 in UTC.
  • Errors share one shape and a stable code — branch on it, never on message:
JSON
{
  "statusCode": 409,
  "code": "INVOICE_NOT_OPEN",
  "message": "Only an open invoice with no payments can be cancelled",
  "requestId": "7c1d…"
}
  • Pagination: list endpoints take limit (1–100, default 25) and startingAfter (the id of the last item you got) and return hasMore. Results are newest first.
  • Idempotency: send Idempotency-Key (any unique string, up to 200 characters) when creating an invoice. Repeating the call with the same key and body returns the original invoice instead of a second one; the same key with a different body is a 409. Keys are remembered for 7 days. Use your order id.
  • Rate limit: 300 calls per minute per key. Above that you get 429 with a Retry-After header.

Invoices

An invoice is one request for payment of a fixed amount. Price it in USD, or in a local currency (BRL, EUR, GBP, JPY, CNY) with currency: the exchange rate is fetched and locked when the invoice is created and you receive the USD equivalent (amountUsd, rounded to the cent). Without a recent rate the request fails with CURRENCY_UNAVAILABLE: retry, or price in USD. Instead of one amount you can send items (up to 50 lines), a discount or one of your coupon codes, and shipping: the total is always recomputed on our side, and an amount that does not match is refused. Its lifecycle:

StatusMeaning
OPENWaiting for payment.
PAIDPaid in full (a small tolerance, 0.5% by default, counts as full).
UNDERPAIDClosed with only part of the amount received. What arrived is already credited to you.
EXPIREDClosed without any payment.
CANCELLEDYou cancelled it (only possible while open and with no payment).
  • An invoice is valid for 60 minutes unless you set expiresInMinutes (5 to 10,080).
  • Pass method ({ "asset": "USDT", "network": "TRC20" }) to skip the coin picker: the checkout opens straight on the QR code.
  • reference is your order number; metadata holds up to 20 simple key/values that come back on every read and webhook.
  • The fee is fixed when the invoice is created (feeBps): changing your rate later never affects an open invoice.
  • Late payments: if money arrives after the invoice closed, or in another coin than chosen, it is still credited to you and flagged late: true — you decide what to do (refund, ship, ignore).
  • Do not treat a visit to your returnUrl as proof of payment. Confirm with the webhook, or by retrieving the invoice.
  • Send it by e-mail: POST /v1/invoices/:id/send mails the invoice (PDF attached, with the payment link) to to or to the invoice customerEmail. In test mode nothing is sent (simulated: true). Up to 10 per minute per key and 100 per day.

Payments & reconciliation

Every credited payment is a row you can list with GET /v1/payments: which invoice, order reference and payment link it came from, the coin and network the customer used, the value in USD at that moment, the fee, the net credited to your wallet and the blockchain transaction hash. GET /v1/summary gives totals, a per-day series, a per-coin split and your conversion rate. The same data is in the dashboard's Statement, with a CSV export.

In the wallet, each payment appears in History as “PoweX Pay payment” with the invoice, order and link it came from — so you can always tell where money came from.

Payouts (paying out)

Your PoweX wallet is a normal wallet, so the money you receive can also be sent on by API: pay a supplier, an affiliate or a customer refund to another PoweX account (recipient: "PWX-123456") or to an address on a blockchain (destination). Check what is available with GET /v1/balance.

curl https://powex.me/v1/payouts \
  -H "Authorization: Bearer pxk_test_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: payout-2026-10-week-42" \
  -d '{
    "asset": "USDT",
    "amount": "25.00",
    "recipient": "PWX-123456",
    "reference": "payout-2026-10-week-42",
    "description": "Weekly payout"
  }'

Safety model

  • Off by default. PoweX enables payouts per merchant after review and sets two ceilings: a maximum per payout and a maximum per 24 hours (in USD). Without that you get PAYOUTS_NOT_ENABLED.
  • Its own scope. payouts:write is separate from everything else; a leaked invoice key cannot move money.
  • Same checks as the app. Balance, the account's daily limit, compliance screening and your address whitelist all apply. Above the amount at which the app asks for a second factor, the API call is refused — make that payment in the app.
  • Idempotency-Key is required. Repeating a request with the same key returns the same payout and never pays twice.
  • Approval for large payouts (optional). In PoweX Pay → Payouts you can set an amount in USD above which a live payout — from your account or any subaccount — is created with status: "AWAITING_APPROVAL" and nothing moves until someone approves it in the dashboard with the PIN. You then get payout.paid / payout.failed as usual. Rejected, or not decided within 72 h, it becomes FAILED. While it waits it counts toward your daily limit. In a batch, such an item comes back as ok with that status.
  • Destination allowlist (recommended). In PoweX Pay → Payouts you can turn on a list of allowed destinations (addresses, with network and memo, or PWX- accounts). While it is on, live payouts — from your account and from all your subaccounts — only go to destinations on it; anything else is refused with PAYOUT_DESTINATION_NOT_ALLOWED. A new destination can receive only 24 h after it is added, turning the list off only takes effect 24 h after the request, and each of these sends you a security alert by Telegram and e-mail — so a leaked key or a stolen session cannot drain the account before you notice. Adding a destination and turning the list off need your PIN; turning it on and removing a destination are immediate. If a destination has a memo, the payout must use the same memo. Test mode is not checked against the list.
  • Travel Rule. At or above the threshold PoweX applies (it depends on the jurisdiction), a live payout to a blockchain address carries who sends and who receives. The sender is your business profile (KYB) — fill it in Settings. The receiver goes in beneficiary (name, walletType: SELF_HOSTED or VASP with vaspName, optional country and personType: INDIVIDUAL or COMPANY) or, once, on the allowlist destination — then payouts to that address need nothing extra. Missing data is refused with TRAVEL_RULE_INFO_REQUIRED before anything moves. Payouts to PWX- accounts and test mode are not affected.
  • The recipient is only ever a PWX- account number — never an e-mail or a username — so the API cannot be used to find out who has an account.
  • You are told by webhook: payout.paid / payout.failed. A payout in REVIEW was interrupted mid-way and PoweX is checking it; it still counts toward your daily limit.

In test mode nothing moves: the payout is returned as PAID so you can build your flow. Everything is listed in Payouts in your dashboard, with your limits and how much of today's ceiling you have used.

Customer addresses

Building a platform, a game or a marketplace? Give each of your customers a permanent deposit address. Whatever arrives there is credited — minus your regular fee — to your own PoweX wallet, and you are told who it was for by the deposit.received webhook, which carries the customerRef you chose. You keep full control of the funds: they are in your wallet, where you can hold, convert, send or withdraw them.

curl https://powex.me/v1/deposit_addresses \
  -H "Authorization: Bearer pxk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "customerRef": "user_8421",
    "asset": "USDT",
    "network": "TRC20"
  }'
  • Same customer, same address. Asking again with the same customerRef, coin and network always returns the same address.
  • Your id, not ours. customerRef is any id you use (1–100 characters). It comes back on every deposit and webhook.
  • Switched on by PoweX. Each customer address has to be watched and swept, so PoweX enables it per merchant and sets how many customers you can have. Without it you get CUSTOMER_ADDRESSES_NOT_ENABLED. Test mode works without it.
  • Test it end to end. In test mode addresses are fake (test_…) and POST /v1/deposit_addresses/:id/simulate_deposit fires the webhook as in production — nothing moves.
  • List them with GET /v1/deposit_addresses and every deposit with GET /v1/customer_deposits (filter by customerRef). See them in the dashboard's Customers tab.
  • A merchant account that is not active does not get new credits: the deposit waits in the customer account until PoweX reviews it. Nothing is lost.

Customers

Keep who pays you in one place, like a CRM: POST /v1/customers (scope customers:write) with name, email, phone (E.164) and a document from any country — BR_CPF, AR_CUIT, MX_RFC… validated with the official check digits and never returned in full (taxIdMasked).

  • ref is your id for the customer, unique per mode (CUSTOMER_REF_TAKEN points to the existing one). The same ref is used by fixed deposit addresses, so a customer and their addresses are one record.
  • Issue an invoice to a customer with customer: "cus_…" — their email fills customerEmail — and list them with GET /v1/invoices?customer=cus_….
  • PATCH /v1/customers/:id changes only what you send; null clears a field. Find customers with ?email= or ?ref=.

Subscriptions

Charge a customer every day, week or month from their PoweX balance. You create the plan; the customer subscribes on the plan page with PIN or biometrics and pays the first cycle on the spot. After that PoweX charges each cycle automatically, at the price and in the coin the customer authorized.

  • POST /v1/subscription_plans (scope subscriptions:write) returns the plan with its url. Send your customer there, adding ?ref=<your id>: it comes back as reference on the subscription and on every subscription.* webhook.
  • Price changes need a new authorization. Archiving a plan (PATCH /v1/subscription_plans/:id with isActive: false) removes its page; current subscribers keep their price and cycle. To change the price, create a new plan.
  • Follow them with GET /v1/subscriptions (filter by status, plan or reference) and stop one with POST /v1/subscriptions/:id/cancel — idempotent, and it never needs the customer.
  • Failed charges are retried 1, 3 and 5 days later (PAST_DUE, the customer is notified); the 4th failure cancels the subscription.
  • Test it end to end. With a test key, plans have no page; use POST /v1/subscription_plans/:id/simulate_subscription and then POST /v1/subscriptions/:id/simulate with paid or failed — the webhooks fire exactly as in production, nothing moves.
  • Platforms can do all of this for a subaccount with the PoweX-Account header.

Subaccounts (platforms)

Running a SaaS, a marketplace or a payment service for other businesses? Open a PoweX Pay account for each of your customers. Every subaccount has its own balance, invoices, payment links and limits — the money of one customer never mixes with another's or with yours — and you operate all of them with your own key.

# 1. Open an account for one of your customers (live ones start PENDING_REVIEW)
curl https://powex.me/v1/accounts \
  -H "Authorization: Bearer pxk_test_…" \
  -H "Idempotency-Key: onboard-store-8421" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ana Coffee",
    "externalRef": "store_8421",
    "business": {
      "type": "COMPANY",
      "legalName": "Ana Coffee Ltda",
      "taxCountry": "BR",
      "taxId": "12.ABC.345/01DE-35",
      "email": "finance@anacoffee.com",
      "address": { "line": "Rua A, 100", "city": "São Paulo", "postalCode": "01001-000", "country": "BR" }
    }
  }'

# 2. Charge on its behalf: same endpoints, plus the PoweX-Account header
curl https://powex.me/v1/invoices \
  -H "Authorization: Bearer pxk_test_…" \
  -H "PoweX-Account: acct_4Fz9QeTt2Lm8Hk1WcY7aNd" \
  -H "Content-Type: application/json" \
  -d '{ "amount": "25.00", "description": "Order #1042" }'
  • Act with one header. Every endpoint of this API works for a subaccount: send PoweX-Account: acct_… with a key that has the accounts:act scope (plus the scope of the call itself). Idempotency keys are per subaccount.
  • One webhook endpoint for everything. Events of a subaccount are also delivered to your endpoints, with an account field naming it. Events of your own account have no account field.
  • Know your customer, anywhere. business is the account holder. Send taxCountry, taxId and optionally taxIdType — e.g. BR_CNPJ, AR_CUIT, CL_RUT, MX_RFC_PM, US_EIN, ES_CIF, DE_VAT, GB_VAT, AU_ABN, IN_GSTIN (full list in the reference). Where the document has an official check-digit algorithm it is verified (the alphanumeric CNPJ included). Without taxIdType the type is inferred from the country; countries without a specific document use OTHER. The tax id is never returned in full (taxIdMasked).
  • Reviewed before it moves money. Live subaccounts start PENDING_REVIEW; PoweX reviews the holder and turns them ACTIVE. While pending, the holder can still be corrected with PATCH /v1/accounts/:id. Test subaccounts start active. Every status change made by PoweX (approval, suspension with its statusNote, reactivation) emits account.updated.
  • Hosted onboarding. Prefer not to handle your customers' tax ids? Create the subaccount without business and call POST /v1/account_links with the account, a refreshUrl and a returnUrl (https). Send the holder to the url you get back: they fill in their own details on a PoweX page and come back to returnUrl. The link is single use and valid for 1 hour; an expired one sends them to refreshUrl to get a new link. A live subaccount is not approved without a holder.
  • Switched on by PoweX. Each subaccount is a real account in the ledger, so PoweX enables subaccounts per platform and sets how many you can have (SUBACCOUNTS_NOT_ENABLED, SUBACCOUNT_LIMIT_REACHED). A subaccount inherits your fee and limits.
  • Use externalRef for your own id and find a subaccount with GET /v1/accounts?externalRef=…. The same holder cannot have two subaccounts on your platform in the same mode.

Moving money

  • Transfers. POST /v1/transfers (scope transfers:write, Idempotency-Key required) moves money between your wallet and a subaccount in one ledger entry: to_account funds it; from_account brings money back and must be enabled by PoweX for your platform (SUBACCOUNT_DEBITS_NOT_ENABLED). Each one emits transfer.created.
  • Payouts from a subaccount. Call POST /v1/payouts with PoweX-Account. A subaccount without its own payout policy uses your platform's (per-payout and daily limits, the daily one counted per subaccount). Check it with GET /v1/balance under the same header.

Your commission and PoweX fees

Every live sale of a subaccount is split when it settles, in the same ledger transaction — invoice paid on-chain, paid with a PoweX balance, or a deposit to a customer's fixed address alike:

  • Your commission (platformFeeBps on the subaccount, 0 to 5000 = 50%) goes to your wallet, in the coin of the sale. Set it on POST /v1/accounts or change it with PATCH /v1/accounts/:id; it applies to the sales that settle afterwards.
  • The PoweX subaccount fee is a per-sale rate PoweX sets for your platform. Depending on your contract it is either taken from the sale (paid by the subaccount) or billed to you in USD (paid by the platform).
  • The payment shows every part: amount = fee + applicationFee + net. Refunds return the PoweX fees proportionally (to the subaccount, or as a credit to you); your commission is not reversed. Nothing is charged in test mode.
  • GET /v1/accounts/fees lists each sale with its commission and PoweX fee (filter by account); GET /v1/accounts/fees/summary gives the period totals and what you currently owe PoweX.

What PoweX bills your platform

  • Charges are listed in GET /v1/accounts/charges: the per-sale fee when you are the payer, refund credits (negative) and, if your contract has one, a monthly fee per live subaccount, billed for each closed month and prorated by the days the subaccount was active (approved and not suspended).
  • Once a day everything open is debited from your wallet in stablecoin in a single entry: USDT, then USDC — combined when neither covers it alone. Each debit emits platform_bill.paid. Keep a stablecoin balance to stay current.
  • Without balance the billing becomes past due (billing.pastDueSince in the fees summary) and you receive platform_billing.past_due once. After the grace period you cannot open new subaccounts (SUBACCOUNT_BILLING_PAST_DUE) until it is settled — existing subaccounts and their money keep working. Settle any time from the dashboard (Pay now).

Webhooks

Register an https:// URL (dashboard or POST /v1/webhook_endpoints) and PoweX will POST a JSON event to it whenever an invoice changes. Pick the events you want, or leave the list empty for all. A test endpoint only receives test-mode events, and a live endpoint only live ones.

EventWhen
invoice.payment_receivedA payment was credited to an invoice. It may be partial; sent for every payment, including late ones.
invoice.paidThe invoice is now paid in full.
invoice.underpaidThe invoice closed with only part of the amount paid.
invoice.expiredThe invoice closed without any payment.
invoice.cancelledYou cancelled the invoice.
payout.paidA payout you requested was delivered (on-chain ones: after confirmation).
payout.failedA payout failed. Nothing was sent and the limit was released.
payment.refundedYou refunded a balance payment back to the customer.
subscription.createdA customer subscribed to one of your plans (first charge paid).
subscription.renewedA subscription was charged again for a new cycle.
subscription.payment_failedA subscription charge failed; PoweX retries 1, 3 and 5 days later.
subscription.cancelledA subscription ended: the customer or you cancelled, or payments kept failing.
deposit.receivedA deposit arrived at one of your customers' addresses and was credited to your wallet.
transfer.createdMoney moved between your wallet and one of your subaccounts.
account.updatedPoweX approved, suspended or reactivated one of your subaccounts; data.previousAttributes has the status before.
platform_bill.paidYour PoweX billing was debited from your stablecoin balance (one leg per coin used).
platform_billing.past_dueA PoweX billing debit failed for lack of balance: the grace period before new subaccounts are blocked starts now.
pingSent by "test" so you can check your endpoint and signature code.
curl https://powex.me/v1/webhook_endpoints \
  -H "Authorization: Bearer pxk_test_…" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://shop.example.com/powex/webhook", "events": ["invoice.paid", "invoice.expired"] }'

The response includes the signing secret (whsec_…) once; rotate it any time (the old one stops working immediately).

The event

JSON
{
  "id": "evt_9f2c4a7b1d3e5f60a8b9c0d1",
  "object": "event",
  "type": "invoice.paid",
  "livemode": true,
  "createdAt": "2026-10-21T16:20:11.000Z",
  "data": {
    "object": {
      "id": "inv_Qm8x2VtFhKz0aBcDeFgHiJ",
      "object": "invoice",
      "status": "PAID",
      "amount": "25",
      "currency": "USD",
      "reference": "order-1042",
      "paidUsd": "25",
      "remainingUsd": "0",
      "payments": [
        {
          "asset": "USDT",
          "amount": "25",
          "fee": "0.125",
          "net": "24.875",
          "usdValue": "25",
          "late": false,
          "txid": "0x…",
          "createdAt": "2026-10-21T16:20:09.000Z"
        }
      ]
    }
  }
}

Headers on every delivery:

HeaderValue
PoweX-Signaturet=<unix>,v1=<hex>
PoweX-Event-IdSame as event.id — identical on every retry.
PoweX-Event-Typee.g. invoice.paid
PoweX-Delivery-Attempt1 for the first try, 2 for the first retry…
User-AgentPoweX-Webhooks/1.0

Verify the signature

Always verify. The signature is HMAC-SHA256(secret, "<t>.<raw body>") as lowercase hex, where t is the unix timestamp in the header. Compute it over the raw request body (before any JSON parsing), compare in constant time and reject timestamps more than 5 minutes old (replay protection). A header may carry several v1= values; accept if any matches.

const crypto = require('node:crypto');

// rawBody must be the EXACT bytes you received (not re-serialized JSON).
function verifyPoweXSignature(secret, header, rawBody, toleranceSeconds = 300, now = Date.now() / 1000) {
  const items = String(header || '').split(',').map((p) => p.trim());
  const t = items.find((p) => p.startsWith('t='))?.slice(2);
  const candidates = items.filter((p) => p.startsWith('v1=')).map((p) => p.slice(3));
  if (!t || !/^\d+$/.test(t) || candidates.length === 0) return false;
  if (Math.abs(now - Number(t)) > toleranceSeconds) return false; // replay protection
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return candidates.some(
    (c) => c.length === expected.length && crypto.timingSafeEqual(Buffer.from(c), Buffer.from(expected)),
  );
}

// Express: use express.raw({ type: 'application/json' }) on this route so req.body is the raw Buffer.
app.post('/powex/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  if (!verifyPoweXSignature(process.env.POWEX_WEBHOOK_SECRET, req.get('PoweX-Signature'), raw)) {
    return res.sendStatus(400);
  }
  const event = JSON.parse(raw);
  // Deduplicate by event.id, do the work, answer fast:
  res.sendStatus(204);
});

Test vector: secret whsec_test_secret_for_docs, t=1700000000, body {"id":"evt_1","object":"event","type":"invoice.paid"} → v1=a38f3edeeb6856c2c5e8bc056b15cdf123db3564ee88a86ab0192debcb7f2d93.

Delivery & retries

  • Answer with any 2xx within 8 seconds. Do slow work after responding.
  • Otherwise we retry up to 8 attempts in total, waiting 30 s, 2 min, 10 min, 1 h, 6 h, 12 h and 24 h between them. After that the delivery is marked failed; you can redeliver it from the dashboard or the API.
  • A 410 Gone response stops the delivery and turns the endpoint off. 40 failed attempts in a row also turn it off (turn it back on from the dashboard).
  • Deduplicate by event.id. Delivery is at-least-once, and events can arrive out of order — read data.object.status rather than assuming sequence.
  • We never follow redirects. The URL must be https, on a public domain name (no IP addresses, no internal hosts), port 443 or 8443 — this is checked when you save it and on every delivery.
  • The delivery log (status, attempts, last error) is kept for 30 days: dashboard, or GET /v1/webhook_endpoints/:id/deliveries.

Errors

Every error has the shape shown in Conventions. The codes you can meet:

CodeHTTPMeaning
API_KEY_INVALID401Missing, malformed, unknown or revoked key.
API_KEY_SCOPE_MISSING403The key lacks the scope this call needs (`details.scope` says which).
API_KEY_IP_NOT_ALLOWED403The key is restricted to a list of IPs and this request came from another one (`details.ip`).
MERCHANT_NOT_ACTIVE403Your merchant account is not approved (live) or is suspended.
PAYOUT_DESTINATION_NOT_ALLOWED403Your payout allowlist is on and this destination is not on it (`details.reason: "not_listed"`) or is still in its 24 h hold (`"on_hold"`, `details.activeAt`).
TRAVEL_RULE_INFO_REQUIRED400At or above the Travel Rule threshold: send `beneficiary` (or set it on the allowlist destination) and fill your business profile. `details.party` is `beneficiary` or `originator`; `details.thresholdUsd` is the threshold.
MERCHANT_LIMIT_EXCEEDED403Above the per-invoice or daily limit set for your account (`details.limit`, `details.max`).
VALIDATION_ERROR400The request body or query is invalid; `message` lists what.
INVOICE_AMOUNT_INVALID400At most 2 decimals, and the USD equivalent must be 0.50–1,000,000.
COUPON_INVALID422The coupon is unknown, inactive, expired, used up, or a fixed coupon in another currency.
CURRENCY_UNAVAILABLE422No recent exchange rate for `currency`. Retry shortly or price in USD.
INVOICE_NOT_FOUND404No such invoice for your account and mode.
INVOICE_NOT_OPEN409The invoice is not open (paid, expired or cancelled), or already has payments.
INVOICE_EXPIRED410The invoice expired.
INVOICE_TEST_ONLY409`simulate` works only on test-mode invoices.
INVOICE_METHOD_UNAVAILABLE409That coin or network is not available right now.
INVOICE_METHOD_LOCKED409A payment already arrived with the chosen method; it cannot change.
PAY_PRICE_UNAVAILABLE503No fresh price for that coin; retry shortly.
PAYMENT_LINK_NOT_FOUND404No such payment link.
PAYMENT_LINK_INACTIVE410The payment link was turned off.
PAYMENT_LINK_BUSY429Too many unpaid invoices are open on that link; retry later.
PAYMENT_LINK_LIMIT_REACHED409You reached the limit of payment links.
PAYOUTS_NOT_ENABLED403PoweX has not enabled payouts for your account (or they are paused).
PAYOUT_LIMIT_EXCEEDED403Above your per-payout or daily payout limit (`details.limit`, `details.max`).
PAYOUT_NOT_FOUND404No such payout for your account and mode.
PAYOUT_IDEMPOTENCY_REQUIRED400Creating a payout needs the `Idempotency-Key` header.
CUSTOMER_ADDRESSES_NOT_ENABLED403PoweX has not enabled customer addresses for your account.
CUSTOMER_ADDRESS_LIMIT_REACHED403You reached the customer limit set for your account (`details.limit`).
CUSTOMER_ADDRESS_NOT_FOUND404No such deposit address for your account and mode.
CUSTOMER_ADDRESS_TEST_ONLY409`simulate_deposit` works only on test-mode addresses.
WEBHOOK_URL_INVALID400The URL is not allowed (needs public https on a domain name).
WEBHOOK_NOT_FOUND404No such webhook endpoint or delivery.
WEBHOOK_LIMIT_REACHED409You reached the limit of webhook endpoints.
RATE_LIMITED429More than 300 calls in a minute with this key; wait `Retry-After` seconds.
DATABASE_BUSY503Momentary overload; nothing was processed — retry.

Quote the requestId when you contact support.

Test mode

  • Test keys, test invoices, test payment links and test webhook endpoints live in a separate world: they never mix with live data.
  • No address is created, nothing is charged and no fee is taken. Use POST /v1/invoices/:id/simulate with paid, underpaid or expired to drive an invoice to any outcome — webhooks fire exactly as in production.
  • simulate is refused (INVOICE_TEST_ONLY) on live invoices.
  • Test subscription plans are subscribed with simulate_subscription and advanced with simulate (SUBSCRIPTION_TEST_ONLY on live ones). Test subscriptions are never charged.
  • You can use test mode while your merchant account is still under review.

Fees & your funds

  • The fee is 0.50% of each payment by default. Your account may have its own rate: it is on every invoice as feeBps (50 = 0.50%).
  • There is no setup fee and no monthly fee. The customer pays the network fee of their own transfer; you pay none to receive.
  • The net amount is credited to your PoweX wallet in the coin the customer paid with. Swap it, send it, or withdraw it — see fees for those operations.
  • Customers with a PoweX account pay in one tap. On the hosted checkout, a logged-in customer can pay with their PoweX balance: no address, no blockchain network, no waiting — the invoice closes at once and the money is in your wallet. For these payments the fee is the lower of your invoice fee and the platform's balance-payment fee (0.25% by default), up to a per-payment ceiling (US$ 1,000 by default; above it the customer pays on-chain). Each payment in GET /v1/payments carries method: chain, balance or simulated.
  • Automatic conversion (optional). In the dashboard you can choose a coin, usually a stablecoin such as USDT: every live payment received in another coin is then converted to it through your wallet's regular swap (its usual fee applies), so you do not carry price swings. Only payments received after you turn it on are converted, and a failed conversion is retried while the funds stay safe in your wallet.
  • Your account can have limits (per invoice and per day) set by PoweX; exceeding them returns MERCHANT_LIMIT_EXCEEDED.

Go-live checklist

  • Merchant account Active (not “Under review”) — required for live keys.
  • A live key with only the scopes you use, stored in your secret manager (not in code or a browser).
  • A webhook endpoint that verifies the signature on the raw body, deduplicates by event id and answers 2xx fast.
  • Idempotency-Key (your order id) on every invoice creation, so a retry never double-bills.
  • Handle invoice.underpaid and late payments (refund, top-up request or manual review).
  • Confirm payment from the webhook or GET /v1/invoices/:id — never from the customer's browser.
  • Reconcile daily with GET /v1/payments or the CSV from the Statement.
  • Run the whole flow in test mode with simulate before switching keys.

Endpoint reference

Generated from the live specification at /v1/openapi.json, so it always matches the API. A * marks a required field; the grey tag on the right is the key scope the call needs.