Payments API Reference
Payments API Reference
Complete reference for the Payment Gateway REST API.
Base URL
https://api.sanpay.io/api/v1 Authentication
All API requests require authentication via Bearer token:
Authorization: Bearer YOUR_API_KEY Payments
Create Payment
POST /payments
Create a new payment request.
Request Body| Field | Type | Required | Description |
|---|---|---|---|
fiatAmount | number | ✅ | Amount in fiat currency |
fiatCurrency | string | ✅ | Fiat currency code: USD, EUR, etc. |
coinId | string (UUID) | ❌ | Specific coin ID. If omitted with allowUserSelection: true, customer chooses. |
chainId | string | ❌ | Blockchain identifier (e.g., BTC, ETH) |
metadata | object | ❌ | Custom key-value pairs |
externalId | string | ❌ | Your internal order/reference ID |
callbackUrl | string | ❌ | URL for webhooks and redirect after payment |
ttlSeconds | integer | ❌ | Time-to-live in seconds (default: 1800) |
allowUserSelection | boolean | ❌ | Allow customer to choose crypto asset (default: false) |
confirmations | integer | ❌ | Required blockchain confirmations |
isTestnet | boolean | ❌ | Use testnet mode (default: false) |
idempotencyKey | string | ❌ | Unique key to prevent duplicate payments |
curl -X POST https://api.sanpay.io/api/v1/payments \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fiatAmount": 99.99,
"fiatCurrency": "USD",
"chainId": "BTC",
"externalId": "order_12345",
"callbackUrl": "https://example.com/webhook"
}' 201 Created {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "PENDING_ASSIGNMENT",
"requestedFiatAmount": 99.99,
"fiatCurrency": "USD",
"requestedCryptoAmount": "0.00250000",
"coinSymbol": "BTC",
"address": null,
"qrData": null,
"paymentUrl": "https://pay.sanpay.io/p/a1b2c3d4",
"fxRateUsed": "39996.00",
"fxLockedAt": "2024-01-15T11:00:00Z",
"fxLockedUntil": "2024-01-15T11:30:00Z",
"expiresAt": "2024-01-15T11:30:00Z",
"createdAt": "2024-01-15T11:00:00Z"
} Get Payment
GET /payments/{paymentId}
Retrieve a payment by ID.
Response200 OK {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "CONFIRMED",
"requestedFiatAmount": 99.99,
"fiatCurrency": "USD",
"requestedCryptoAmount": "0.00250000",
"coinSymbol": "BTC",
"address": "bc1qxy2kgdygjrsqvzq3rj...",
"txHash": "abc123def456...",
"confirmations": 6,
"confirmedAt": "2024-01-15T11:15:00Z",
"createdAt": "2024-01-15T11:00:00Z"
} Payment Statuses
| Status | Description |
|---|---|
PENDING_ASSIGNMENT | Payment created, waiting for address assignment |
PENDING_SELECTION | Waiting for customer to select crypto asset (deferred selection) |
AWAITING_PAYMENT | Address assigned, waiting for customer to send funds |
DETECTED | Transaction seen in mempool (0 confirmations) |
PARTIALLY_PAID | Partial payment received, awaiting remaining amount |
CONFIRMING | Transaction received, waiting for required confirmations |
CONFIRMED | Payment confirmed with required block confirmations |
CONFIRMED_PARTIAL | Partial amount confirmed |
SETTLED | Payment fully settled — final success state |
PARTIALLY_SETTLED | Partial amount settled |
EXPIRED | Payment window expired with no payment |
EXPIRED_PARTIAL | Payment window expired with partial payment received |
CANCELLED | Payment cancelled by merchant |
REFUND_PENDING_ADDRESS | Refund initiated, waiting for customer refund address |
REFUND_PROCESSING | Refund transaction being processed |
REFUNDED | Refund completed successfully |
REFUND_FAILED | Refund transaction failed |
REFUND_EXPIRED | Refund address collection expired |
REORG_DETECTED | Blockchain reorganization detected — payment under review |
List Payments
GET /payments
List all payments for your account.
Query Parameters| Field | Type | Description |
|---|---|---|
status | string | Filter by status |
externalId | string | Filter by order ID |
since | string | ISO 8601 timestamp |
until | string | ISO 8601 timestamp |
limit | integer | Max results (default: 20, max: 100) |
offset | integer | Pagination offset |
200 OK {
"data": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "SETTLED",
"requestedFiatAmount": 99.99,
"fiatCurrency": "USD",
"requestedCryptoAmount": "0.00250000",
"coinSymbol": "BTC",
"createdAt": "2024-01-15T11:00:00Z"
}
],
"pagination": {
"total": 156,
"limit": 20,
"offset": 0
}
} Checkout Sessions
Create Checkout Session
POST /checkout/sessions
Create a hosted checkout session.
Request Body| Field | Type | Required | Description |
|---|---|---|---|
fiatAmount | number | ✅ | Amount in fiat currency |
fiatCurrency | string | ✅ | Fiat currency code: USD, EUR, etc. |
description | string | ❌ | Order description |
externalId | string | ❌ | Your internal order ID for correlation |
successUrl | string | ✅ | Redirect URL on success |
cancelUrl | string | ✅ | Redirect URL on cancel |
customerEmail | string | ❌ | Pre-fill customer email |
ttlSeconds | integer | ❌ | Time-to-live in seconds (default: 1800) |
201 Created {
"id": "cs_xyz789",
"url": "https://checkout.sanpay.io/cs_xyz789",
"expiresAt": "2024-01-15T12:00:00Z"
} Refunds
Create Refund
POST /payments/{paymentId}/refunds
Record a refund for a payment.
Request Body| Field | Type | Required | Description |
|---|---|---|---|
amount | string | ✅ | Refund amount in crypto |
txHash | string | ❌ | Refund transaction hash |
reason | string | ❌ | Reason for refund |
201 Created {
"id": "ref_abc123",
"paymentId": "pay_abc123",
"amount": "0.00250000",
"currency": "BTC",
"txHash": "abc123...",
"status": "completed",
"createdAt": "2024-01-16T10:00:00Z"
} Webhooks
Payment events are delivered to your webhook URL:
| Event | Description |
|---|---|
PAYMENT_CREATED | Payment request created |
PAYMENT_ADDRESS_ASSIGNED | Crypto address assigned to payment |
PAYMENT_DETECTED | Transaction seen in mempool |
PAYMENT_CONFIRMATION_UPDATE | Confirmation count updated |
PAYMENT_CONFIRMED | Payment reached required confirmations |
PAYMENT_SETTLED | Payment fully settled — final success event |
PAYMENT_EXPIRED | Payment window expired |
PAYMENT_EXPIRED_PARTIAL | Payment expired with partial amount received |
PAYMENT_CANCELLED | Payment cancelled by merchant |
PAYMENT_REFUND_INITIATED | Refund process started |
PAYMENT_REFUND_COMPLETED | Refund completed successfully |
PAYMENT_REFUND_FAILED | Refund transaction failed |
PAYMENT_TX_UNMATCHED | Transaction received but could not be matched |
PAYMENT_REORG | Blockchain reorganization detected |
PAYMENT_LATE_DETECTED | Payment detected after expiry |
Webhook Payload
{
"eventId": "evt-a1b2c3d4-e5f6",
"eventType": "PAYMENT_CONFIRMED",
"timestamp": "2024-01-15T11:15:00Z",
"tenantId": "your-tenant-id",
"paymentRequestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"address": "bc1qxy2kgdygjrsqvzq3rj...",
"coinSymbol": "BTC",
"chainId": "BTC",
"status": "CONFIRMED",
"confirmations": 6,
"requiredConfirmations": 3,
"requestedFiatAmount": 99.99,
"fiatCurrency": "USD",
"requestedCryptoAmount": "0.00250000"
} Error Responses
{
"error": {
"code": "PAYMENT_EXPIRED",
"message": "The payment request has expired",
"details": {
"paymentId": "pay_abc123",
"expiredAt": "2024-01-15T11:30:00Z"
}
}
} Payment Error Codes
| Code | Status | Description |
|---|---|---|
PAYMENT_NOT_FOUND | 404 | Payment does not exist |
PAYMENT_EXPIRED | 400 | Payment has expired |
INVALID_AMOUNT | 400 | Invalid amount format |
UNSUPPORTED_CURRENCY | 400 | Currency not supported |
RATE_LIMITED | 429 | Too many requests |
Cancel Payment
POST /payments/{paymentId}/cancel
Cancel a payment request. Only payments in `AWAITING_PAYMENT` or `PENDING_SELECTION` status can be cancelled. Payments with detected transactions cannot be cancelled.
Response200 OK {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "CANCELLED",
"cancelledAt": "2026-01-15T11:20:00Z",
"reason": "Customer requested cancellation"
} Get Checkout Session
GET /checkout/sessions/{sessionId}
Retrieve a checkout session by ID.
Response200 OK {
"id": "cs_xyz789",
"status": "completed",
"paymentId": "pay_abc123",
"amount": "99.99",
"currency": "USD",
"successUrl": "https://example.com/success",
"cancelUrl": "https://example.com/cancel",
"expiresAt": "2024-01-15T12:00:00Z",
"createdAt": "2024-01-15T11:00:00Z"
} Expire Checkout Session
DELETE /checkout/sessions/{sessionId}
Expire a checkout session immediately. The session can no longer be used for payment.
Response200 OK {
"id": "cs_xyz789",
"status": "expired",
"expiredAt": "2024-01-15T11:30:00Z"
} List Refunds
GET /payments/{paymentId}/refunds
List all refunds for a specific payment.
Response200 OK {
"data": [
{
"id": "ref_abc123",
"paymentId": "pay_abc123",
"amount": "0.00125000",
"currency": "BTC",
"status": "completed",
"reason": "Customer request",
"createdAt": "2024-01-16T10:00:00Z"
}
],
"pagination": {
"total": 1,
"limit": 20,
"offset": 0
}
} Get Refund
GET /refunds/{refundId}
Retrieve a refund by ID.
Response200 OK {
"id": "ref_abc123",
"paymentId": "pay_abc123",
"amount": "0.00125000",
"currency": "BTC",
"txHash": "abc123def456...",
"status": "completed",
"reason": "Customer request",
"createdAt": "2024-01-16T10:00:00Z"
} Payment Links
Payment links are reusable URLs that allow customers to pay any amount. Ideal for donations, tips, or invoices.
Create Payment Link
POST /payment-links
Create a reusable payment link.
Request Body| Field | Type | Required | Description |
|---|---|---|---|
name | string | ✅ | Display name for the payment link |
amount | string | ❌ | Fixed amount (if omitted, customer enters amount) |
currency | string | ✅ | Fiat currency code: USD, EUR, etc. |
description | string | ❌ | Description shown to customer |
redirectUrl | string | ❌ | URL to redirect after payment |
metadata | object | ❌ | Custom key-value pairs |
201 Created {
"id": "plink_abc123",
"name": "Premium Subscription",
"url": "https://pay.sanpay.io/l/plink_abc123",
"amount": "99.99",
"currency": "USD",
"active": true,
"createdAt": "2024-01-15T11:00:00Z"
} List Payment Links
GET /payment-links
List all payment links for your account.
Query Parameters| Field | Type | Description |
|---|---|---|
active | boolean | Filter by active status |
limit | integer | Max results (default: 20, max: 100) |
offset | integer | Pagination offset |
200 OK {
"data": [
{
"id": "plink_abc123",
"name": "Premium Subscription",
"url": "https://pay.sanpay.io/l/plink_abc123",
"amount": "99.99",
"currency": "USD",
"active": true,
"paymentsCount": 42,
"totalCollected": "4199.58",
"createdAt": "2024-01-15T11:00:00Z"
}
],
"pagination": {
"total": 5,
"limit": 20,
"offset": 0
}
} Get Payment Link
GET /payment-links/{linkId}
Retrieve a payment link by ID.
Deactivate Payment Link
DELETE /payment-links/{linkId}
Deactivate a payment link. The link URL will no longer accept payments.
Response200 OK {
"id": "plink_abc123",
"active": false,
"deactivatedAt": "2024-01-20T15:00:00Z"
} Exchange Rates
Get real-time cryptocurrency exchange rates.
Get Exchange Rates
GET /exchange-rates
Retrieve current exchange rates for supported cryptocurrencies.
Query Parameters| Field | Type | Description |
|---|---|---|
base | string | Base fiat currency (default: USD) |
symbols | string | Comma-separated crypto symbols (e.g., BTC,ETH) |
200 OK {
"base": "USD",
"timestamp": "2024-01-15T11:00:00Z",
"rates": {
"BTC": "0.000025",
"ETH": "0.00045",
"USDT": "1.0001",
"USDC": "0.9999"
}
} Account
Get Account Balance
GET /account/balance
Retrieve your current account balances across all cryptocurrencies.
Response200 OK {
"balances": [
{
"currency": "BTC",
"available": "1.25000000",
"pending": "0.05000000",
"total": "1.30000000"
},
{
"currency": "ETH",
"available": "15.50000000",
"pending": "0.00000000",
"total": "15.50000000"
}
],
"updatedAt": "2024-01-15T11:00:00Z"
} Rate Limiting
API requests are rate-limited to ensure fair usage. Rate limit information is included in response headers.
Rate Limit Headers
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed per window |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
Default Rate Limits
| Endpoint | Limit | Window |
|---|---|---|
| POST /payments | 100 | 1 minute |
| GET /payments | 300 | 1 minute |
| POST /checkout/sessions | 100 | 1 minute |
| GET /exchange-rates | 60 | 1 minute |
Idempotency
Use idempotency keys to safely retry requests without risking duplicate operations.
Using Idempotency Keys
Include an `idempotencyKey` field in the request body with a unique value (e.g., UUID) for POST requests:
curl -X POST https://api.sanpay.io/api/v1/payments \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"fiatAmount": 99.99, "fiatCurrency": "USD", "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000"}' Idempotency Behavior
- Keys are valid for 24 hours after the first request
- Subsequent requests with the same key return the original response
- Keys are scoped to your API key
- We recommend using UUID v4 for idempotency keys
API Examples
Here are examples of creating a payment in popular programming languages:
Node.js
const response = await fetch('https://api.sanpay.io/api/v1/payments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SANPAY_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
fiatAmount: 99.99,
fiatCurrency: 'USD',
externalId: 'ORDER-12345',
description: 'Premium Plan'
})
});
const payment = await response.json(); Python
import requests
response = requests.post(
'https://api.sanpay.io/api/v1/payments',
headers={
'Authorization': f'Bearer {os.environ["SANPAY_API_KEY"]}',
'Content-Type': 'application/json'
},
json={
'fiatAmount': 99.99,
'fiatCurrency': 'USD',
'externalId': 'ORDER-12345',
'description': 'Premium Plan'
}
)
payment = response.json() PHP
$ch = curl_init('https://api.sanpay.io/api/v1/payments');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('SANPAY_API_KEY'),
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'fiatAmount' => 99.99,
'fiatCurrency' => 'USD',
'externalId' => 'ORDER-12345',
'description' => 'Premium Plan'
])
]);
$payment = json_decode(curl_exec($ch), true);