Developer Integration Guide
Clean RESTful API with HMAC-SHA256 authentication — average 1-day integration
Quick Start
Complete payment integration in 5 steps
Register and Get Your API Key
Sign up for a merchant account, create a project in the dashboard, and obtain your Access Key and Secret Key.
Configure Webhook URL
Set up the Webhook notification URL in your project settings. The system will push notifications when order status changes.
Call the Create Order API
Use HMAC-SHA256 to sign your request and call POST /payments to get a receiving address and checkout link.
Handle Webhook Notifications
Receive Webhook notifications, verify the X-BS-Signature, and update your local order status.
Test and Go Live
Complete end-to-end testing in the sandbox environment, then switch to production once verified.
HMAC-SHA256 Signature Authentication
Each request must include an Access Key, timestamp, random Nonce, and HMAC signature to ensure requests are unforgeable.
Async Notifications (Webhook)
Supports 8 event types including payment.completed, order.expired, and payment.underpaid — covering the full payment lifecycle.
12-State Order State Machine
PENDING → PAID_UNCONFIRMED → CONFIRMED → COMPLETED, with exception branches (underpaid/overpaid/expired/risk control) — clear and controllable state transitions.
Idempotency & Replay Prevention
Nonce prevents replay attacks, merchant_order_no prevents duplicate creation, event_id prevents duplicate processing — triple idempotency protection.
API Authentication
All merchant API requests require HMAC-SHA256 signature authentication
Required Headers
| Header | Description | Example |
|---|---|---|
X-BS-Access-Key | Merchant Access Key (public key), identifies the merchant | ak_live_xxxxxxxxxxxx |
X-BS-Timestamp | Request timestamp (Unix seconds), must be within 5 minutes of server time | 1704067200 |
X-BS-Nonce | Random string (UUID) for replay prevention, unique per request | 550e8400-e29b-41d4-a716-446655440000 |
X-BS-Signature | HMAC-SHA256 signature (Hex encoded) | a1b2c3d4e5f6... |
Content-Type | Fixed as JSON format | application/json |
Signature Algorithm
// 1. Construct signature body
signature_body = "{timestamp}" + "." + "{nonce}" + "." + "{request_body}"
// 2. Compute HMAC-SHA256 with Secret Key
signature = HMAC-SHA256(signature_body, secret_key)
// 3. Convert signature to Hex string, set as X-BS-Signature header
X-BS-Signature = hex(signature)
// Example:
// timestamp = "1704067200"
// nonce = "550e8400-e29b-41d4-a716-446655440000"
// body = '{"merchant_order_no":"ORD-001","fiat_amount":"100.00",...}'
// secret_key = "sk_live_your_secret_key"
// signature_body = "1704067200.550e8400-e29b-41d4-a716-446655440000.{...json...}"API Reference Documentation
Base URL: https://api.blocksettler.com/api/v1/merchant
/paymentsCreate Payment OrderRequest Parameters (Request Body)
| Field | Type | Required | Description |
|---|---|---|---|
merchant_order_no | string | Yes | Merchant order number, unique within the project (max 64 chars) |
title | string | Yes | Order title, displayed to the payer (max 128 chars) |
description | string | No | Order description (max 512 chars) |
fiat_amount | string | Yes | Fiat amount, e.g. "100.00" |
fiat_currency | string | Yes | Fiat currency, e.g. "USD", "CNY" |
pay_chain | string | No | Payment chain: POLYGON / TRON / ETH / BSC / SOLANA. Uses project default if not specified |
pay_token | string | No | Payment token: USDT / USDC. Uses project default if not specified |
notify_url | string | No | Async notification URL. Uses project default if not specified |
return_url | string | No | Frontend redirect URL after payment completion |
expires_in | int | No | Order validity period (seconds), range 300~86400, default 1800 (30 min) |
metadata | object | No | Custom merchant data, returned as-is in async notifications |
Response Parameters (Response Body)
| Field | Type | Description |
|---|---|---|
platform_order_no | string | Platform order number (unique ID generated by BlockSettler) |
merchant_order_no | string | Merchant order number (as provided in the request) |
status | string | Order status, initially "PENDING" |
fiat_amount | string | Fiat amount |
fiat_currency | string | Fiat currency |
pay_amount | string | Crypto amount to pay (calculated at real-time exchange rate) |
paid_amount | string | Crypto amount already paid (0 upon creation) |
pay_chain | string | Payment chain name |
pay_token | string | Payment token |
pay_address | string | Receiving address (payer must transfer to this address) |
expired_at | string | Order expiration time (ISO 8601 format) |
created_at | string | Creation time |
Request Example
// Request Body
{
"merchant_order_no": "ORD-20250415-0001",
"title": "Premium Plan - 1 Month",
"description": "Premium membership subscription",
"fiat_amount": "99.00",
"fiat_currency": "USD",
"pay_chain": "TRON",
"pay_token": "USDT",
"notify_url": "https://your-site.com/api/payment/notify",
"return_url": "https://your-site.com/payment/success",
"expires_in": 1800,
"metadata": {
"user_id": "U-10086",
"plan": "premium"
}
}Response Example
{
"code": 200,
"message": "success",
"data": {
"platform_order_no": "BS2025041500000001",
"merchant_order_no": "ORD-20250415-0001",
"status": "PENDING",
"fiat_amount": "99.00",
"fiat_currency": "USD",
"pay_amount": "99.000000000000000000",
"paid_amount": "0.000000000000000000",
"pay_chain": "TRON",
"pay_token": "USDT",
"pay_address": "TN7gK3Wfxy4RDXG5DPGnXmkJJPqozvMBkR",
"return_url": "https://your-site.com/payment/success",
"expired_at": "2025-04-15T11:30:00Z",
"created_at": "2025-04-15T11:00:00Z"
}
}/payments/:order_noQuery Order DetailsQuery the latest order status by platform order number. :order_no = platform_order_no
Response Example (After Payment)
{
"code": 200,
"message": "success",
"data": {
"platform_order_no": "BS2025041500000001",
"merchant_order_no": "ORD-20250415-0001",
"status": "COMPLETED",
"fiat_amount": "99.00",
"fiat_currency": "USD",
"pay_amount": "99.000000000000000000",
"paid_amount": "99.000000000000000000",
"pay_chain": "TRON",
"pay_token": "USDT",
"pay_address": "TN7gK3Wfxy4RDXG5DPGnXmkJJPqozvMBkR",
"expired_at": "2025-04-15T11:30:00Z",
"paid_at": "2025-04-15T11:05:32Z",
"confirmed_at": "2025-04-15T11:06:45Z",
"completed_at": "2025-04-15T11:06:45Z",
"created_at": "2025-04-15T11:00:00Z"
}
}/payments/:order_no/closeClose OrderClose a pending payment order. Only orders in PENDING, UNDERPAID, OVERPAID, LATE_PAID status can be closed.
Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | No | Reason for closing (max 128 chars) |
Response Format & Error CodesAll API endpoints return a unified JSON structure:
// Success response
{ "code": 200, "message": "success", "data": { ... } }
// Invalid parameters
{ "code": 400, "message": "invalid fiat_amount", "data": null }
// Authentication failed (invalid signature / Access Key / expired timestamp)
{ "code": 401, "message": "invalid signature", "data": null }
// Resource not found
{ "code": 404, "message": "order not found", "data": null }
// Internal server error
{ "code": 500, "message": "internal error", "data": null }Async Notifications (Webhook)
When an order status changes, BlockSettler sends an HTTP POST request to the merchant's configured callback URL
Supported Event Types
| event_type | Trigger | Description |
|---|---|---|
payment.confirmed | On-chain confirmations reached | Transaction confirmed, safe to deliver goods/services |
payment.completed | Order settlement completed | Order flow completed normally |
order.expired | Order timed out without payment | Validity period exceeded without full payment |
order.closed | Order closed | Closed by merchant or system |
payment.underpaid | Insufficient payment amount | Amount received is less than required |
payment.overpaid | Excess payment amount | Amount received exceeds required amount |
payment.late_paid | Payment received after expiry | Order expired but on-chain payment still received |
payment.failed | Payment failed | Risk control review rejected or other anomaly |
Notification Payload Format
The system sends a POST request to the merchant's notify_url with Content-Type application/json.
Notification Headers
| Header | Description |
|---|---|
Content-Type | application/json |
X-BS-Event-ID | Unique event ID for idempotent deduplication |
X-BS-Event-Type | Event type, e.g. payment.completed |
X-BS-Timestamp | Notification timestamp (Unix seconds) |
X-BS-Signature | HMAC-SHA256 signature (for verifying notification source) |
Notification Body Example (payment.completed)
{
"event_id": "evt_8f14e45f-ceea-4a4d-a8e0-3c2b5d4e9f01",
"event_type": "payment.completed",
"timestamp": "2025-04-15T11:06:45Z",
"platform_order_no": "BS2025041500000001",
"status": "COMPLETED",
"payment": {
"fiat_amount": "99.00",
"fiat_currency": "USD",
"pay_amount": "99.000000",
"paid_amount": "99.000000",
"pay_chain": "TRON",
"pay_token": "USDT",
"pay_address": "TN7gK3Wfxy4RDXG5DPGnXmkJJPqozvMBkR",
"tx_hash": "6a1b2c3d4e5f...abc123def456"
}
}Signature Verification
After receiving a notification, the merchant must verify the X-BS-Signature to confirm the notification source is trusted. Verification method:
// 1. Get timestamp and signature from request headers
timestamp = request.headers["X-BS-Timestamp"]
signature = request.headers["X-BS-Signature"]
// 2. Read the raw request body (JSON string)
raw_body = request.body
// 3. Construct signature body and compute HMAC-SHA256
expected = HMAC-SHA256(timestamp + "." + raw_body, your_webhook_secret)
// 4. Use constant-time comparison to verify signature (prevent timing attacks)
is_valid = constant_time_equal(hex(expected), signature)Retry Mechanism
Success Criteria
Merchant endpoint returns HTTP 2xx status code
Retry Strategy
Exponential backoff: 1s → 5s → 30s → 5m → 30m → 2h, max 6 retries
Idempotent Processing
Use event_id for idempotent checks — the same event may be delivered multiple times
Code Samples
Complete examples for creating orders (with signing) and receiving Webhook notifications
# ====== Create Payment Order ======
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen)
BODY='{"merchant_order_no":"ORD-20250415-0001","title":"Premium Plan","fiat_amount":"99.00","fiat_currency":"USD","pay_chain":"TRON","pay_token":"USDT","notify_url":"https://your-site.com/api/notify"}'
# Compute signature: HMAC-SHA256(timestamp.nonce.body, secret_key)
SIGN_BODY="${TIMESTAMP}.${NONCE}.${BODY}"
SIGNATURE=$(echo -n "${SIGN_BODY}" | openssl dgst -sha256 -hmac "sk_live_xxxx" | awk '{print $2}')
curl -X POST https://api.blocksettler.com/api/v1/merchant/payments \
-H "Content-Type: application/json" \
-H "X-BS-Access-Key: ak_live_xxxxxxxxxxxx" \
-H "X-BS-Timestamp: ${TIMESTAMP}" \
-H "X-BS-Nonce: ${NONCE}" \
-H "X-BS-Signature: ${SIGNATURE}" \
-d "${BODY}"
# ====== Query Order ======
curl -X GET https://api.blocksettler.com/api/v1/merchant/payments/BS2025041500000001 \
-H "X-BS-Access-Key: ak_live_xxxxxxxxxxxx" \
-H "X-BS-Timestamp: ${TIMESTAMP}" \
-H "X-BS-Nonce: $(uuidgen)" \
-H "X-BS-Signature: {computed_signature}"Order Status Transitions
BlockSettler uses a 12-state machine to manage the order lifecycle
CREATEDCreatedPENDINGPending PaymentPAID_UNCONFIRMEDPaid, UnconfirmedCONFIRMEDConfirmedCOMPLETEDCompletedEXPIREDExpiredCLOSEDClosedUNDERPAIDUnderpaidOVERPAIDOverpaidLATE_PAIDLate PaymentRISK_REVIEWRisk ReviewFAILEDFailed Normal Payment Flow
CREATED ──> PENDING ──> PAID_UNCONFIRMED ──> CONFIRMED ──> COMPLETED
│
│ Exception Branches
├──> EXPIRED Timed out without payment
├──> CLOSED Closed by merchant
├──> UNDERPAID Paid < Required
├──> OVERPAID Paid > Required
└──> LATE_PAID Payment after expiry
EXPIRED ──> LATE_PAID Payment received after expiry
PAID_UNCONFIRMED ──> RISK_REVIEW Risk control triggered
RISK_REVIEW ──> CONFIRMED / FAILED Approved / RejectedReady to Start Integrating?
Register for an API Key, test in the sandbox environment, and go live in 1 day on average