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:
| Key | Prefix | Usage |
|---|---|---|
Secret key | sk_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 key | pk_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
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.
| Field | Type | Description |
|---|---|---|
amountUsdrequired | string | Amount in USD as a string, e.g. "149.99" — avoids floating-point issues. |
orderId | string | Your internal order ID, for correlation (max 255 chars). |
metadata | object | Arbitrary key-value metadata, returned in API responses and webhooks. |
successUrl | string | URL the customer is redirected to after successful payment. |
cancelUrl | string | URL the customer is redirected to if they cancel. |
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" }'
{
"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
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)
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.
| Field | Type | Description |
|---|---|---|
chainrequired | string | One of TRON, BASE, POLYGON. |
{
"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"
}Pay with card
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)
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
| Field | Type | Description |
|---|---|---|
reasonrequired | string | customer_request, duplicate, fraud, or free text. |
amount | number | USD amount to refund. Omit for a full refund. |
notes | string | Internal notes, visible in your dashboard. |
autoProcess | boolean | Default 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
Returns all refunds plus a summary: paymentAmount, totalRefunded, remaining.
Merchant API — account & reporting
All merchant endpoints authenticate with your secret key.
| Endpoint | Description |
|---|---|
POST /merchant/login | Email + password login. Returns your profile and key prefixes (never the full secret key). |
GET /merchant/me | Profile, keys metadata, webhook config, payment stats. |
GET /merchant/dashboard | Aggregated dashboard data: volume, recent payments, balances. |
GET /merchant/payments | List your payments. |
GET /merchant/balance | Current ledger balance. |
GET /merchant/ledger | Ledger entries (credits, debits, fees). |
GET /merchant/settlements | Settlement batches and their status. |
GET /merchant/refunds | All refunds across payments. |
PATCH /merchant/settings | Update webhook URL, payout addresses, and other settings. |
Key rotation
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:
| Event | Fired when |
|---|---|
payment.confirmed | Crypto deposit reached the required confirmations. |
payment.card_captured | Card payment captured by the card partner. |
payment.treasury_received | Funds arrived in the AssetPay treasury — the payment is final. |
payment.completed | Payment fully completed end-to-end. |
payment.settled | Included in a settlement batch paid out to you. |
payment.expired | Customer never paid before expiry. |
payment.failed | Processing failed (card declined, forwarding error). |
payment.refund_requested | A 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.
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
Card flow
CARD_3DS_REQUIRED only occurs when the issuer requests 3-D Secure.
Terminal & error states
Supported chains
| Chain | Token | Notes |
|---|---|---|
TRON | USDT (TRC-20) | Low fees, fast confirmation. |
BASE | USDC | Ethereum L2, low gas. |
POLYGON | USDC | Fast and affordable. |
Errors
Errors return a consistent envelope with an HTTP status code:
{
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": { "amountUsd": ["Required"] }
}
}| Status | Code | Meaning |
|---|---|---|
400 | invalid_request | Validation failed — see details. |
401 | authentication_failed | Missing or invalid API key. |
404 | not_found | Resource doesn't exist or belongs to another merchant. |
409 | invalid_state_transition | Action not allowed in the payment's current state. |
410 | payment_expired | The payment window has closed. |
429 | rate_limited | Too many requests — back off and retry. |