DEVELOPER REFERENCE

API Documentation

Everything you need to accept card and crypto payments with AssetPay. One REST API, predictable JSON responses, webhook events for every state change.

Base URL

All API requests are made over HTTPS to your environment's base URL. Responses are JSON.

# Production
https://api.assetpay.io

# Local / sandbox
http://localhost:3001

Authentication

AssetPay uses two API keys, issued when your merchant account is approved:

KeyPrefixUsage
Secret keysk_live_Server-side only. Sent as Authorization: Bearer sk_live_xxx. Full access: create payments, refunds, account data. Never expose it in browsers or mobile apps.
Publishable keypk_live_Safe for browsers. Sent as X-Publishable-Key: pk_live_xxx. Limited to customer-facing checkout actions (chain selection, card submission, public payment view).
# Secret key (server-side)
curl https://api.assetpay.io/merchant/me \
  -H "Authorization: Bearer sk_live_xxx"

# Publishable key (browser)
curl https://api.assetpay.io/payments/pay_123 \
  -H "X-Publishable-Key: pk_live_xxx"

Idempotency

Pass an Idempotency-Key header on POST /payments to safely retry requests without creating duplicate payments. Responses are cached for 24 hours — replays return the original response.

curl -X POST https://api.assetpay.io/payments \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Idempotency-Key: order-8512-attempt-1" \
  -H "Content-Type: application/json" \
  -d '{"amountUsd": "149.99"}'

Create a payment

POST/paymentsSecret key

Creates a payment and returns a hosted checkoutUrl to redirect your customer to. The checkout page lets the customer choose crypto (TRON, Base, Polygon) or card.

Body parameters
FieldTypeDescription
amountUsdrequiredstringAmount in USD as a string, e.g. "149.99" — avoids floating-point issues.
orderIdstringYour internal order ID, for correlation (max 255 chars).
metadataobjectArbitrary key-value metadata, returned in API responses and webhooks.
successUrlstringURL the customer is redirected to after successful payment.
cancelUrlstringURL the customer is redirected to if they cancel.
Request
curl -X POST https://api.assetpay.io/payments \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "amountUsd": "149.99",
    "orderId": "order-8512",
    "successUrl": "https://yourstore.com/thanks",
    "cancelUrl": "https://yourstore.com/cart"
  }'
Response — 201 Created
{
  "id": "pay_2ZxK8qW4tN1vR7mC",
  "status": "CREATED",
  "amountUsd": "149.99",
  "checkoutUrl": "https://pay.assetpay.io/pay/pay_2ZxK8qW4tN1vR7mC",
  "expiresAt": "2026-06-12T08:30:00.000Z",
  "createdAt": "2026-06-12T08:00:00.000Z"
}

Retrieve a payment

GET/payments/:idSecret or publishable key

Returns the payment. The view depends on which key you authenticate with:

Secret key → full detail: fees, forwarding transactions, metadata, settlement state.
Publishable key → customer-facing subset: status, amount, deposit address, confirmations.

Select chain (crypto)

POST/payments/:id/select-chainPublishable key

Called from checkout when the customer picks a network. Derives a unique deposit address, locks the exchange rate, and moves the payment to AWAITING_PAYMENT.

Body parameters
FieldTypeDescription
chainrequiredstringOne of TRON, BASE, POLYGON.
Response — 200 OK
{
  "id": "pay_2ZxK8qW4tN1vR7mC",
  "status": "AWAITING_PAYMENT",
  "chain": "TRON",
  "token": "USDT",
  "depositAddress": "TXk4...9fQz",
  "cryptoAmount": "149.99",
  "exchangeRate": "1.0000",
  "rateExpiresAt": "2026-06-12T08:15:00.000Z",
  "expiresAt": "2026-06-12T08:30:00.000Z"
}
Deposits are only accepted from whitelisted token contracts. Any transfer whose token contract doesn't exactly match the official USDT / USDC contract for that chain is rejected.

Pay with card

POST/payments/:id/pay-cardPublishable key

Submits card details from the hosted checkout. The payment moves to CARD_PROCESSING and, if the issuer requires it, CARD_3DS_REQUIRED with a redirect URL for 3-D Secure. After authorization and capture, funds flow to treasury like any other payment.

Live payment events (SSE)

GET/payments/:id/eventsNo auth — payment ID is the session token

Server-Sent Events stream for real-time checkout updates. Emits the public payment object whenever status or confirmation count changes, and closes automatically on terminal states.

const es = new EventSource(`${API}/payments/${id}/events`);
es.onmessage = (e) => {
  const payment = JSON.parse(e.data);
  render(payment.status, payment.confirmations);
};

Request a refund

POST/payments/:id/refundSecret key
Body parameters
FieldTypeDescription
reasonrequiredstringcustomer_request, duplicate, fraud, or free text.
amountnumberUSD amount to refund. Omit for a full refund.
notesstringInternal notes, visible in your dashboard.
autoProcessbooleanDefault false — refunds stay PENDING for review. When true: card refunds process immediately via the card partner; crypto refunds auto-approve.

List refunds for a payment

GET/payments/:id/refundsSecret key

Returns all refunds plus a summary: paymentAmount, totalRefunded, remaining.

Merchant API — account & reporting

All merchant endpoints authenticate with your secret key.

EndpointDescription
POST /merchant/loginEmail + password login. Returns your profile and key prefixes (never the full secret key).
GET /merchant/meProfile, keys metadata, webhook config, payment stats.
GET /merchant/dashboardAggregated dashboard data: volume, recent payments, balances.
GET /merchant/paymentsList your payments.
GET /merchant/balanceCurrent ledger balance.
GET /merchant/ledgerLedger entries (credits, debits, fees).
GET /merchant/settlementsSettlement batches and their status.
GET /merchant/refundsAll refunds across payments.
PATCH /merchant/settingsUpdate webhook URL, payout addresses, and other settings.

Key rotation

POST/merchant/keys/rotate-secretSecret key
POST/merchant/keys/rotate-webhookSecret key

Rotating the secret key invalidates the old key immediately and returns the new one once — store it securely. Rotating the webhook secret returns a new signing secret for webhook verification.

Webhook events

AssetPay POSTs a JSON event to your configured webhook URL on every significant state change:

EventFired when
payment.confirmedCrypto deposit reached the required confirmations.
payment.card_capturedCard payment captured by the card partner.
payment.treasury_receivedFunds arrived in the AssetPay treasury — the payment is final.
payment.completedPayment fully completed end-to-end.
payment.settledIncluded in a settlement batch paid out to you.
payment.expiredCustomer never paid before expiry.
payment.failedProcessing failed (card declined, forwarding error).
payment.refund_requestedA refund was requested for the payment.

Verifying signatures

Every webhook includes an X-AssetPay-Signature header in the format t=<timestamp>,v1=<hmac>. The HMAC is SHA-256 over "<timestamp>.<raw_body>" using your webhook secret. Reject events older than 5 minutes to prevent replay.

Node.js
import { createHmac } from 'node:crypto';

function verifyWebhook(secret, rawBody, signatureHeader) {
  const match = signatureHeader.match(/^t=(\d+),v1=([0-9a-f]{64})$/);
  if (!match) return false;

  const [, ts, received] = match;
  // Reject stale events (> 300 s)
  if (Math.abs(Date.now() / 1000 - ts) > 300) return false;

  const expected = createHmac('sha256', secret)
    .update(`${ts}.${rawBody}`)
    .digest('hex');
  return expected === received;
}

Payment lifecycle

Crypto flow

CREATEDAWAITING_PAYMENTDETECTEDCONFIRMINGCONFIRMEDFORWARDINGTREASURY_RECEIVED

Card flow

CREATEDCARD_PROCESSINGCARD_3DS_REQUIREDCARD_AUTHORIZEDCARD_CAPTUREDTREASURY_RECEIVED

CARD_3DS_REQUIRED only occurs when the issuer requests 3-D Secure.

Terminal & error states

COMPLETEDSETTLEDEXPIREDUNDERPAIDFAILEDREFUNDEDCHARGED_BACK

Supported chains

ChainTokenNotes
TRONUSDT (TRC-20)Low fees, fast confirmation.
BASEUSDCEthereum L2, low gas.
POLYGONUSDCFast and affordable.

Errors

Errors return a consistent envelope with an HTTP status code:

{
  "error": {
    "code": "invalid_request",
    "message": "Invalid request body",
    "details": { "amountUsd": ["Required"] }
  }
}
StatusCodeMeaning
400invalid_requestValidation failed — see details.
401authentication_failedMissing or invalid API key.
404not_foundResource doesn't exist or belongs to another merchant.
409invalid_state_transitionAction not allowed in the payment's current state.
410payment_expiredThe payment window has closed.
429rate_limitedToo many requests — back off and retry.