Setup & Onboarding
Get started with Baynpay in three simple steps. Complete these administrative requirements before integrating our API into your application.
Create Your Merchant Account
You will receive the onboarding email from the team to register and log in to the Baynpay portal. The Merchant Admin role will be assigned to this user, granting the highest authority on the platform. Once registered and signed in, you may start adding your team members.
Generate API Credentials
After approval, navigate to Settings → API Keys in your dashboard to generate:
- API Key (
key_dfljsdlfu...): Use for frontend and client-side operations - API Secret (
hex_1o3sld...): Use for backend and server-side operations (keep secure!)
Environment URLs
Production: https://api.baynpay.com
Sandbox: https://sandbox.baynpay.comStart with the sandbox environment to test your integration before going live.
Authentication
All Baynpay API requests require HMAC-SHA256 authentication to ensure request integrity and prevent tampering. This cryptographic approach validates that requests originate from authorized clients.
Required Headers
Include these headers in every authenticated API request:
- X-API-Key: Your API Key ID (e.g.,
key_abc123...) - X-Timestamp: Current epoch time in milliseconds (e.g.,
1702512000000) - X-Nonce: Unique UUID v7 identifier (e.g.,
550e8400-e29b-...) - X-Body-Hash: SHA-256 hash of request body (e.g.,
a665a459...) - X-Signature: HMAC-SHA256 signature (e.g.,
f7bc83f4...)
Generating Authentication Signatures
Follow these three steps to create a valid request signature:
Step 1: Calculate Body Hash
Compute the SHA-256 hash of your request body:
import hashlib
body = '{"client_id": "123", "amount": "100.50"}'
body_hash = hashlib.sha256(body.encode('utf-8')).hexdigest()Step 2: Build Canonical String
Construct a canonical string by joining these components with newline characters:
{HTTP_METHOD}
{PATH}
{TIMESTAMP}
{NONCE}
{BODY_HASH}
Step 3: Compute HMAC Signature
Follow these steps to generate the final signature:
- Remove the
hex_prefix from your API Key Secret - Decode the hexadecimal string to bytes
- Compute HMAC-SHA256 using the decoded key and canonical string
- Encode the result as a hexadecimal string
- Include the signature in the
X-Signatureheader
Complete Implementation
Python Example
Here's a full Python example that handles authentication:
import hmac
import hashlib
import uuid
from datetime import datetime
import requests
def make_authenticated_request(method, endpoint, api_key_id, api_key_secret, body=""):
timestamp = str(int(datetime.now().timestamp() * 1000))
nonce = str(uuid.uuid4())
body_hash = hashlib.sha256(body.encode('utf-8')).hexdigest()
path = endpoint.split('?')[0]
canonical = f"{method}\n{path}\n\n{timestamp}\n{nonce}\n{body_hash}"
secret_hex = api_key_secret.replace('hex_', '')
secret_bytes = bytes.fromhex(secret_hex)
signature = hmac.new(secret_bytes, canonical.encode('utf-8'), hashlib.sha256).hexdigest()
headers = {
'X-API-Key': api_key_id,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Body-Hash': body_hash,
'X-Signature': signature
}
return requests.request(method, "{env_url}{endpoint}", headers=headers, data=body)JavaScript Example for Postman Pre-script
// ===== CONFIG =====
const apiKeyId = "key_3085fcfa34224---------------";
const apiSecretHex = "a9808e48a7----------------f18"; // WITHOUT "hex_" prefix
// ==================
// Timestamp & Nonce
const timestamp = Date.now().toString();
const nonce = pm.variables.replaceIn('{{$guid}}');
// HTTP method
const method = pm.request.method;
// URL parts - properly extract path
const url = pm.request.url;
const path = "/" + url.path.filter(p => p).join("/");
// Query string - properly handle query parameters
let query = "";
if (url.query && url.query.count() > 0) {
const enabledParams = url.query.filter(q => !q.disabled);
if (enabledParams.length > 0) {
query = enabledParams.map(q => `${encodeURIComponent(q.key)}=${encodeURIComponent(q.value || '')}`).join("&");
}
}
// Body (empty string for GET)
let body = "";
if (pm.request.body && pm.request.body.raw) {
body = pm.request.body.raw;
}
// SHA-256 body hash using CryptoJS (available in Postman sandbox)
const bodyHash = CryptoJS.SHA256(body).toString(CryptoJS.enc.Hex);
// Build canonical string
const stringToSign = [
method,
path,
query,
timestamp,
nonce,
bodyHash
].join("\n");
// Parse the hex secret key properly
const secretKeyBytes = CryptoJS.enc.Hex.parse(apiSecretHex);
// HMAC SHA256 signature
const signature = CryptoJS.HmacSHA256(stringToSign, secretKeyBytes).toString(CryptoJS.enc.Hex);
// Debug logging
console.log("=== HMAC DEBUG ===");
console.log("Method:", method);
console.log("Path:", path);
console.log("Query:", query);
console.log("Timestamp:", timestamp);
console.log("Nonce:", nonce);
console.log("Body Hash:", bodyHash);
console.log("String to Sign:");
console.log(stringToSign);
console.log("Signature:", signature);
console.log("==================");
// Set headers
pm.request.headers.upsert({ key: "X-API-Key", value: apiKeyId });
pm.request.headers.upsert({ key: "X-Timestamp", value: timestamp });
pm.request.headers.upsert({ key: "X-Nonce", value: nonce });
pm.request.headers.upsert({ key: "X-Body-Hash", value: bodyHash });
pm.request.headers.upsert({ key: "X-Signature", value: signature });Security Considerations
- Timestamp Validation: Requests must be made within 5 minutes of the current server time to prevent replay attacks.
- Nonce Uniqueness: Each request uses a UUID v7 nonce to ensure one-time use and prevent duplicate submissions.
The API Reference page includes a built-in HMAC authentication panel that automatically generates and signs test requests for you.
Clients & Assets
Register your end-users as clients in Baynpay before processing any transactions. Each client receives a dedicated wallet with unique deposit addresses for supported cryptocurrencies.
Create a Client
Register a new end-user in your Baynpay merchant account.
Endpoint: POST /api/v1/clients
Request Body:
{
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"phone": "+97141234567",
"client_reference_id": "123784567-test1-234729374",
"address": {
"address_line1": "Burj Khalifa",
"address_line2": "Downtown",
"city": "Dubai",
"state_code": "DU",
"postal_code": "25314",
"country_code": "AE"
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
first_name | string | Yes | Client's first name |
last_name | string | Yes | Client's last name |
email | string | Yes | Unique email address |
phone | string | Optional | Phone number in E.164 format |
client_reference_id | string | Yes | Your internal reference (order ID, invoice number) |
address | object | Yes | Country code and state code are required |
Response:
{
"status": "success",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"merchant": {
"id": "019a4f-4de-75f-91-7ed38ca3f7a0",
"name": "Principia Mathematica Ltd"
},
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"phone": "+97141234567",
"status": "active",
"client_reference_id": "123784567-test1-234729374",
"kyc_rejection_labels": [],
"kyc_review_status": "",
"kyc_review_result": "",
"kyc_risk_score": 0,
"address": {
"address_line1": "Burj Khalifa",
"address_line2": "Downtown",
"city": "Dubai",
"state_code": "DU",
"postal_code": "25314",
"country_code": "AE"
},
"created_at": "2025-12-17T10:30:00Z",
"updated_at": "2025-12-17T10:30:00Z"
}
}⚠️ Important: Store the id field from the response—you'll need it for all subsequent API calls for this client.
List Clients
Retrieve all clients with pagination support.
Endpoint: GET /api/v1/clients
Response:
{
"status": "success",
"data": {
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"merchant": {
"id": "019a4f-4de-75f-91-7ed38ca3f7a0",
"name": "Principia Mathematica Ltd"
},
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"phone": "+97141234567",
"status": "active",
"client_reference_id": "123784567-test1-234729374",
"kyc_rejection_labels": [],
"kyc_review_status": "",
"kyc_review_result": "",
"kyc_risk_score": 0,
"address": {
"address_line1": "Burj Khalifa",
"address_line2": "Downtown",
"city": "Dubai",
"state_code": "DU",
"postal_code": "25314",
"country_code": "AE"
},
"created_at": "2025-12-17T10:30:00Z",
"updated_at": "2025-12-17T10:30:00Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"totalItems": 50,
"totalPages": 5
}
}
}Get a Client
Retrieve detailed information for a specific client.
Endpoint: GET /api/v1/clients/{client_id}
The response structure matches the client object shown in the List Clients endpoint.
Countries & Assets
List Supported Countries
Retrieve all supported countries and state codes for client address validation.
Endpoint: GET /api/v1/countries
Response:
{
"status": "success",
"data": [
{
"id": "89d34b3-f352-4f35-af23-51dee85365bb",
"name": "United Arab Emirates",
"code": "AE",
"phone_code": "+971",
"is_active": true,
"states": [
{
"id": "8c783ed8-445e-4810-8e5c-2f725f98174b",
"country_id": "89d34b3-f352-4f35-af23-51dee85365bb",
"name": "Dubai",
"code": "DU",
"is_active": true
}
]
}
]
}Get Supported Assets
View all cryptocurrency assets enabled for your merchant account.
Endpoint: GET /api/v1/assets
Response:
{
"status": "success",
"data": {
"items": [
{
"chain": "TRON",
"assets": ["TRX", "USDT"]
},
{
"chain": "POLYGON",
"assets": ["POL", "USDB"]
}
]
}
}Deposits & Withdrawals
Generate quotes for crypto deposits or withdrawals. All quotes lock exchange rates for 5 minutes to protect against price volatility during the transaction window.
Understanding Transaction Types
- Deposits: Users send cryptocurrency and receive fiat currency after fees are deducted from the total amount.
- Withdrawals: Users receive the exact cryptocurrency amount requested. All fees are charged separately in USD.
Create Deposit
Initiate a deposit transaction where the user sends cryptocurrency to receive fiat.
Endpoint: POST /api/v1/quote
Request:
{
"client_id": "550e8400-e29b-41d4-a716-446655440001",
"client_reference_id": "123784567-test1-234729374",
"order_reference_id": "order-12345",
"send": {
"currency": "USDT",
"amount": 1000
},
"receive": {
"currency": "USD"
},
"direction": "DEPOSIT",
"chain": "TRON"
}Response:
{
"status": "success",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440003",
"client_id": "550e8400-e29b-41d4-a716-446655440001",
"client_reference_id": "123784567-test1-234729374",
"order_reference_id": "order-12345",
"send": {
"currency": "USDT",
"amount": 1000.0
},
"receive": {
"currency": "USD",
"amount": 926.50
},
"fees": {
"transaction_fee": 5.00,
"platform_fee": 50.00,
"service_fee": 13.50,
"tax": 5.00,
"total_fees": 73.50,
"fee_currency": "USD"
},
"chain": "TRON",
"symbol": "USDT",
"direction": "DEPOSIT",
"status": "PENDING",
"compliance": {},
"transaction_id": "",
"received_amount": 0.00,
"created_at": "2025-12-17T10:30:00Z",
"expires_at": "2025-12-17T10:35:00Z"
}
}In this example, the user sends 1000 USDT and receives 926.50 USD after fees.
Get Quote by ID
Track the status of a specific deposit quote.
Endpoint: GET /api/v1/quote/{quote_id}
Response:
{
"status": "success",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440003",
"client_id": "550e8400-e29b-41d4-a716-446655440001",
"client_reference_id": "123784567-test1-234729374",
"order_reference_id": "order-12345",
"send": {
"currency": "USDT",
"amount": 1000.0
},
"receive": {
"currency": "USD",
"amount": 926.50
},
"fees": {
"transaction_fee": 5.00,
"platform_fee": 50.00,
"service_fee": 13.50,
"tax": 5.00,
"total_fees": 73.50,
"fee_currency": "USD"
},
"chain": "TRON",
"symbol": "USDT",
"direction": "DEPOSIT",
"status": "ACCEPTED",
"deposit_address": "TXYZa1b2c3d4e5f6g7h8i9j0k1l2m3n4o5",
"compliance": {},
"transaction_id": "",
"received_amount": 0.00,
"created_at": "2025-12-17T10:30:00Z",
"expires_at": "2025-12-17T10:35:00Z",
"accepted_at": "2025-12-17T10:31:00Z"
}
}Once accepted, the deposit_address field provides the wallet address where the user should send funds.
Create Withdrawal
Initiate a withdrawal where the user receives the exact cryptocurrency amount. Fees are charged separately.
Endpoint: POST /api/v1/quote
Request:
{
"client_id": "550e8400-e29b-41d4-a716-446655440001",
"client_reference_id": "123784567-test1-234729374",
"order_reference_id": "order-12345",
"send": {
"currency": "USDT",
"amount": 1000
},
"receive": {
"currency": "USDT"
},
"direction": "WITHDRAWAL",
"chain": "TRON",
"client_address": "TXYZa1b2c3d4e5f6g7h8i9j0k1l2m3n4o5"
}Response:
{
"status": "success",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440004",
"client_id": "550e8400-e29b-41d4-a716-446655440001",
"client_reference_id": "123784567-test1-234729374",
"order_reference_id": "order-12345",
"send": {
"currency": "USDT",
"amount": 1000.0
},
"receive": {
"currency": "USDT",
"amount": 1000.0
},
"fees": {
"transaction_fee": 5.00,
"platform_fee": 50.00,
"service_fee": 13.50,
"tax": 5.00,
"gas_fee": 10.00,
"total_fees": 83.50,
"fee_currency": "USD"
},
"chain": "TRON",
"symbol": "USDT",
"direction": "WITHDRAWAL",
"status": "PENDING",
"deposit_address": "TXYZa1b2c3d4e5f6g7h8i9j0k1l2m3n4o5",
"compliance": {},
"transaction_id": "",
"received_amount": 0.00,
"created_at": "2025-12-17T10:30:00Z",
"expires_at": "2025-12-17T10:35:00Z"
}
}Get Withdrawal by ID
Endpoint: GET /api/v1/quote/{quote_id}
The response structure matches the withdrawal object with updated status information.
Quote Status Lifecycle
| Status | Description |
|---|---|
ACCEPTED | Quote created, deposit address assigned, awaiting payment |
PAID | Exact quoted amount received |
OVERPAID | Received more than quoted amount |
UNDERPAID | Received less than quoted amount |
EXPIRED | No on-chain transaction detected within validity period |
Transactions
View and manage all transactions processed through your Baynpay merchant account. Monitor deposits, settlements, and real-time transaction status updates.
List Transactions
Retrieve a paginated list of all transactions for your merchant account.
Endpoint: GET /api/v1/transactions
Response:
{
"data": {
"items": [
{
"amount": 100,
"block_confirmations": 1,
"block_height": 30679480,
"blockchain_txn_hash": "0x1aa26fda62e88650893c764ec36e955fc5e1257c148f28a2c019f5f0293bbb",
"chain": "TRON",
"client_id": "550e8400-e29b-41d4-a716-446655440001",
"client_reference_id": "123784567-test1-234729374",
"completed_at": "2025-12-17T17:13:41+05:30",
"created_at": "2025-12-17T17:13:40+05:30",
"destination_account": "0xE59d396E22630yzaDB4DC2b00werwieaaf483DE",
"fees": {
"fee_currency": "USD",
"platform_fee": 0.1,
"service_fee": 0.1,
"tax": 0.1,
"total_fees": 0.5,
"transaction_fee": 0.2
},
"id": "0192c1f-8445-7395-9f40-c472fc07f50a",
"merchant_id": "019a4f-4de-75f-91-7ed38ca3f7a0",
"source_account": "0xB442EE1D67436c7Aa273a1EAf27b64DE67D546C",
"status": "COMPLETED",
"symbol": "USDT",
"txn_type": "DEPOSIT",
"updated_at": "2025-12-17T17:13:42.269083+05:30"
}
],
"pagination": {
"page": 1,
"limit": 10,
"totalItems": 100,
"totalPages": 10
}
},
"error": null,
"status": "success"
}Get a Transaction
Retrieve detailed information for a specific transaction.
Endpoint: GET /api/v1/transactions/{transaction_id}
Response:
{
"data": {
"amount": 100,
"block_confirmations": 1,
"block_height": 30679480,
"blockchain_txn_hash": "0x1aa26fda62e88650893c764ec36e955fc5e1257c148f28a2c019f5f0293bbbc",
"chain": "TRON",
"client_id": "550e8400-e29b-41d4-a716-446655440001",
"client_reference_id": "123784567-test1-234729374",
"completed_at": "2025-12-17T17:13:41+05:30",
"created_at": "2025-12-17T17:13:40+05:30",
"destination_account": "0xE59d396E22630yzaDB4DC2b00werwieaaf483DE",
"fees": {
"fee_currency": "USD",
"platform_fee": 0.1,
"service_fee": 0.1,
"tax": 0.1,
"total_fees": 0.5,
"transaction_fee": 0.2
},
"id": "0192c1f-8445-7395-9f40-c472fc07f50a",
"merchant_id": "019a4f-4de-75f-91-7ed38ca3f7a0",
"source_account": "0xB442EE1D67436c7Aa273a1EAf27b64DE67D546C",
"status": "COMPLETED",
"symbol": "USDT",
"txn_type": "DEPOSIT",
"updated_at": "2025-12-17T17:13:42.269083+05:30"
},
"status": "success",
"error": null
}Transaction Status Values
| Status | Description |
|---|---|
CREATED | Transaction initiated on blockchain |
PENDING | Awaiting block confirmations and verification |
COMPLETED | Transaction confirmed, funds transferred successfully |
FAILED | Transaction failed due to error or rejection |
HTTP Status Codes
| Code | Meaning |
|---|---|
200 | Success |
400 | Bad Request - Invalid parameters |
401 | Unauthorized - Invalid or missing API key |
404 | Not Found - Resource doesn't exist |
409 | Conflict - Quote already confirmed or expired |
422 | Unprocessable Entity - Business logic error |
429 | Rate Limited - Too many requests |
500 | Server Error - Contact support |
Webhooks
Webhooks enable real-time notifications for events in your Baynpay account. When significant actions occur—such as transaction status changes—Baynpay sends HTTP POST requests to your registered endpoints, allowing your application to respond immediately.
Transaction Lifecycle
┌──────────────────────────────────────────────────────────────────────────┐
│ TRANSACTION STATUS LIFECYCLE │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────┐ ┌──────────┐ ┌───────────┐ ┌───────────┐ │
│ │ CREATED │───▶│ PENDING │───▶│ COMPLETED │ │ FAILED │ │
│ └───────────┘ └──────────┘ └───────────┘ └───────────┘ │
│ │ │ │ ▲ │
│ │ │ │ │ │
│ └───────────────┴───────────────┴─────────────────┘ │
│ (on error) │
│ │
└──────────────────────────────────────────────────────────────────────────┘Webhook Setup
Create an HTTP Endpoint
Set up an HTTPS endpoint on your server that:
- Accepts HTTP POST requests
- Is accessible via HTTPS (HTTP not supported for security)
- Returns a 2xx status code upon successful receipt
- Responds within 30 seconds
Register the Webhook
Register your endpoint through the Baynpay platform:
- Navigate to Developer Center → Webhook Manager
- Click Add Webhook Endpoint
- Enter your HTTPS endpoint URL
- Select the events you want to receive
- Save the webhook secret provided (required for signature verification)
Webhook Events
Baynpay sends webhooks for these transaction events:
| Event Type | Description | When Triggered |
|---|---|---|
transaction.created | New transaction initiated | When a deposit or withdrawal is first created |
transaction.status.updated | Transaction status changed | When transaction moves to pending, completed, or failed |
Understanding the Transaction Flow
Every transaction progresses through specific states, each triggering a webhook event:
Created: Transaction initiated and submitted to blockchain
Event: transaction.created
Status: CREATED
Sent only once after initial creation
Pending: Transaction processing on blockchain
Event: transaction.status.updated
Status: PENDING
Blockchain hash available, awaiting confirmations
Completed: Transaction successfully confirmed
Event: transaction.status.updated
Status: COMPLETED
Required confirmations reached, funds transferred
Failed: Transaction rejected or failed
Event: transaction.status.updated
Status: FAILED
Could not complete (insufficient funds, network issues, etc.)
📋 Webhook Count: You receive exactly three webhooks per transaction: one for creation, one for pending status, and one for final completion or failure.
Event Payload Structure
Each webhook includes two ID fields:
- webhook_id: Unique webhook delivery log ID (use for idempotency)
- transaction_id: The actual transaction ID
transaction.created
{
"webhook_id": "0199999-aaaa-bbbb-cccc-dddddddddddd",
"event": "transaction.created",
"timestamp": "2024-12-14T08:25:00Z",
"data": {
"block_confirmations": 1,
"block_height": 30729292,
"transaction_id": "0191234-5678-90ab-cdef-1234567890ab",
"merchant_id": "0190987-6543-21fe-dca-0987654321fe",
"client_id": "0195555-6666-7777-8888-999900001111",
"client_reference_id": "ORDER-2024-12345",
"txn_type": "DEPOSIT",
"status": "CREATED",
"amount": "100.50",
"symbol": "USDT",
"chain": "TRON",
"source_account": "0x742d35Cc6634C0532925a3844Bc9e7595f0Eb",
"destination_account": "0x8626f6940E2e28930eF4CeF49B2d1F2C9C1199",
"created_at": "2024-12-14T08:25:00Z",
"updated_at": "2024-12-14T08:25:00Z",
"completed_at": "2024-12-14T08:26:00Z",
"fees": {
"fee_currency": "USD",
"platform_fee": 0.1,
"service_fee": 0.1,
"tax": 0.1,
"total_fees": 0.5,
"transaction_fee": 0.2
},
"quote_id": "",
"quote_status": "",
"order_reference_id": "order-12345"
}
}transaction.status.updated (pending)
{
"webhook_id": "0199987-aaaa-bbbb-cccc-dddddddddddd",
"event": "transaction.status.updated",
"timestamp": "2024-12-14T08:26:30Z",
"data": {
"block_confirmations": 1,
"block_height": 30729292,
"transaction_id": "0191234-5678-90ab-cdef-1234567890ab",
"merchant_id": "0190987-6543-21fe-dca-0987654321fe",
"client_id": "0195555-6666-7777-8888-999900001111",
"client_reference_id": "ORDER-2024-12345",
"txn_type": "DEPOSIT",
"status": "PENDING",
"amount": "100.50",
"symbol": "USDT",
"chain": "TRON",
"source_account": "0x742d35Cc6634C0532925a3844Bc9e7595f0Eb",
"destination_account": "0x8626f6940E2e28930eF4CeF49B2d1F2C9C1199",
"created_at": "2024-12-14T08:25:00Z",
"updated_at": "2024-12-14T08:25:00Z",
"completed_at": "2024-12-14T08:26:00Z",
"fees": {
"fee_currency": "USD",
"platform_fee": 0.1,
"service_fee": 0.1,
"tax": 0.1,
"total_fees": 0.5,
"transaction_fee": 0.2
},
"quote_id": "019860-6f82-7819-8e1e-d4aa4fe3ff31",
"quote_status": "OVERPAID",
"order_reference_id": "order-12345"
}
}transaction.status.updated (completed)
{
"webhook_id": "019777-aaaa-bbbb-cccc-dddddddddddd",
"event": "transaction.status.updated",
"timestamp": "2024-12-14T08:30:00Z",
"data": {
"block_confirmations": 1,
"block_height": 30729292,
"transaction_id": "0191234-5678-90ab-cdef-1234567890ab",
"merchant_id": "0190987-6543-21fe-dca-0987654321fe",
"client_id": "0195555-6666-7777-8888-999900001111",
"client_reference_id": "ORDER-2024-12345",
"txn_type": "DEPOSIT",
"status": "COMPLETED",
"amount": "100.50",
"symbol": "USDT",
"chain": "TRON",
"source_account": "0x742d35Cc6634C0532925a3844Bc9e7595f0Eb",
"destination_account": "0x8626f6940E2e28930eF4CeF49B2d1F2C9C1199",
"created_at": "2024-12-14T08:25:00Z",
"updated_at": "2024-12-14T08:25:00Z",
"completed_at": "2024-12-14T08:26:00Z",
"fees": {
"fee_currency": "USD",
"platform_fee": 0.1,
"service_fee": 0.1,
"tax": 0.1,
"total_fees": 0.5,
"transaction_fee": 0.2
},
"quote_id": "019860-6f82-7819-8e1e-d4aa4fe3ff31",
"quote_status": "OVERPAID",
"order_reference_id": "order-12345"
}
}transaction.status.updated (failed)
{
"webhook_id": "019555-aaaa-bbbb-cccc-dddddddddddd",
"event": "transaction.status.updated",
"timestamp": "2024-12-14T08:30:00Z",
"data": {
"block_confirmations": 1,
"block_height": 30729292,
"transaction_id": "0191234-5678-90ab-cdef-1234567890ab",
"merchant_id": "0190987-6543-21fe-dca-0987654321fe",
"client_id": "0195555-6666-7777-8888-999900001111",
"client_reference_id": "ORDER-2024-12345",
"txn_type": "WITHDRAWAL",
"status": "FAILED",
"amount": "100.50",
"symbol": "USDT",
"chain": "TRON",
"source_account": "0x742d35Cc6634C0532925a3844Bc9e7595f0Eb",
"destination_account": "0x8626f6940E2e28930eF4CeF49B2d1F2C9C1199",
"created_at": "2024-12-14T08:25:00Z",
"updated_at": "2024-12-14T08:25:00Z",
"completed_at": "2024-12-14T08:26:00Z",
"fees": {},
"quote_id": "019860-6f82-7819-8e1e-d4aa4fe3ff31",
"quote_status": "OVERPAID",
"order_reference_id": "order-12345"
}
}Webhook Security
Every webhook request includes security headers to verify authenticity. Validate these signatures to ensure requests originate from Baynpay.
Request Headers
| Header | Description |
|---|---|
Content-Type | Always application/json |
X-Webhook-Signature | Base64-encoded HMAC-SHA256 signature |
X-Webhook-Event | Event type (e.g., transaction.created) |
X-Webhook-Timestamp | Unix timestamp (seconds) when webhook was sent |
User-Agent | Always VisionPay-Webhooks/1.0 |
Signature Verification
The signature is computed as:
base64(hmac_sha256(secret, "{timestamp}.{json_payload}"))Follow these verification steps:
- Extract X-Webhook-Timestamp and X-Webhook-Signature headers
- Concatenate timestamp and raw JSON payload:
{timestamp}.{payload} - Compute HMAC-SHA256 using your webhook secret
- Base64-encode the result
- Compare with received signature using constant-time comparison
Complete Implementation Example
import hmac
import hashlib
import base64
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret_here"
def verify_webhook_signature(payload_body, signature, timestamp):
"""Verify webhook signature."""
# Construct signature data
signature_data = f"{timestamp}.{payload_body}"
# Compute HMAC-SHA256
expected_signature = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
signature_data.encode('utf-8'),
hashlib.sha256
).digest()
# Base64 encode
expected_signature_b64 = base64.b64encode(expected_signature).decode('utf-8')
# Constant-time comparison
return hmac.compare_digest(expected_signature_b64, signature)
@app.route('/webhooks/baynpay', methods=['POST'])
def handle_webhook():
# Get raw body and headers
payload_body = request.get_data(as_text=True)
signature = request.headers.get('X-Webhook-Signature')
timestamp = request.headers.get('X-Webhook-Timestamp')
event_type = request.headers.get('X-Webhook-Event')
# Verify signature
if not verify_webhook_signature(payload_body, signature, timestamp):
return jsonify({'error': 'Invalid signature'}), 401
# Parse event
event = request.json
# Process event based on type
if event_type == 'transaction.created':
handle_transaction_created(event)
elif event_type == 'transaction.status.updated':
handle_transaction_status_updated(event)
return jsonify({'received': True}), 200
def handle_transaction_created(event):
transaction = event['data']
print(f"New transaction created: {transaction['transaction_id']}")
print(f"Type: {transaction['txn_type']}, Amount: {transaction['amount']} {transaction['symbol']}")
# Add your business logic here
def handle_transaction_status_updated(event):
transaction = event['data']
status = transaction['status']
print(f"Transaction {transaction['transaction_id']} status: {status}")
if status == 'PENDING':
print(f"Confirmations: {transaction['block_confirmations']}")
# Update order status to processing
elif status == 'COMPLETED':
print(f"Transaction completed at: {transaction['completed_at']}")
# Mark order as complete, release goods/services
elif status == 'FAILED':
print(f"Transaction failed")
# Handle failure, refund, notify customer
if __name__ == '__main__':
app.run(port=3000)Retry Policy
If your endpoint fails to respond with a 2xx status code, Baynpay automatically retries delivery:
- Retry Attempts: Up to 3 retries
- Backoff Strategy: Exponential (1 minute, 2 minutes, 4 minutes)
- Timeout: 30 seconds per attempt
- Total Duration: Up to 7 minutes for all retry attempts
Best Practices
Respond Immediately
Return a 200 status code as soon as you receive and validate the webhook. Process the event asynchronously to avoid timeouts.
Implement Idempotency
Use the webhook_id to track processed events and prevent duplicate processing if webhooks are delivered multiple times:
# Use a database in production (Redis, PostgreSQL, etc.)
processed_events = set()
def handle_webhook(event):
webhook_id = event['webhook_id']
# Check if already processed
if webhook_id in processed_events:
return # Skip duplicate
# Process event
process_transaction(event)
# Mark as processed
processed_events.add(webhook_id)Validate Timestamps
Reject webhooks with timestamps too far in the past (e.g., older than 5 minutes) to prevent replay attacks.
Log All Webhooks
Maintain audit logs of all received webhooks for debugging and compliance.
Monitor Failures
Set up alerts for failed webhook deliveries. Check the Webhook Logs dashboard regularly to identify and resolve delivery issues.
Test Before Production
Test webhook signature verification with sample payloads from your test environment to ensure your verification logic works correctly. Use the webhook secret from your test environment.
📊 Webhook Logs: Access these logs in Developer Center → Webhook Logs to monitor delivery status and troubleshoot issues.
Test Token Faucet
Baynpay provides a test token faucet for developers to claim USDB tokens on Polygon testnet. This enables you to test your integration with realistic transaction flows without using real cryptocurrency or incurring actual costs.
Faucet Token Details
| Property | Value |
|---|---|
| Token Name | BaynPay USD Test |
| Token Symbol | USDB |
| Blockchain Network | Polygon testnet |
| Contract Address | 0x11238A992c00E6dF615F6bbCded8198CAe84ece2 |
How to Use the Faucet
Access the faucet through Developer Center → Faucet Tokens in your merchant portal:
- Enter your Polygon wallet address (must start with
0x) - Specify the token amount you need (between 1 and 10,000 tokens)
- Click Claim Tokens to receive test USDB instantly
Key Features
- On-demand claiming: Request tokens whenever you need them during development
- Flexible amounts: Claim between 1 and 10,000 tokens per request
- Instant delivery: Tokens are sent immediately to your provided Polygon wallet address
When to Use
- Testing deposit flows by sending test USDB to client wallets
- Validating withdrawal processes without risking real funds
- Demonstrating Baynpay functionality to stakeholders
- Training team members on transaction workflows
- Verifying webhook delivery and transaction status updates
Error Code List
Baynpay uses standardized error codes to help you diagnose and handle issues in your integration.
1. VALIDATION_ERROR
When it occurs
- Missing required fields
- Invalid request payload
- Invalid query parameters
- Incorrect request structure
Evidence
API calls failing with 400 status, explicit "code": "VALIDATION_ERROR" present in response payloads
HTTP Status
400 – Bad Request
2. UNAUTHORIZED
When it occurs
- Missing authentication token
- Invalid or expired credentials
Example Response
{
"success": false,
"message": "Unauthorized"
}HTTP Status
401 – Unauthorized
3. ACCESS_DENIED
When it occurs
- Role-based access violation
- Insufficient permissions for the attempted action
Example Response
{
"success": false,
"message": "Access denied. Role MerchantAdmin from group External does not have permission..."
}HTTP Status
403 – Forbidden
4. NOT_FOUND
When it occurs
- Invalid or non-existent resource ID
- Client not found
- Incorrect endpoint usage
Example Response
{
"success": false,
"message": "Not Found"
}HTTP Status
404 – Not Found
5. QUOTE_CREATION_FAILED
When it occurs
- Withdrawal amount exceeds available balance
- Invalid withdrawal conditions
- Business-rule violations during quote generation
Example Response
{
"data": null,
"error": {
"code": "QUOTE_CREATION_FAILED",
"message": "insufficient funds: available balance 611.50000000 VTEST, requested 1200.00000000 VTEST"
}
}HTTP Status
400 – Bad Request
Error Code Set
| Error Code | HTTP Status | Category |
|---|---|---|
VALIDATION_ERROR | 400 | Client / Input |
UNAUTHORIZED | 401 | Authentication |
ACCESS_DENIED | 403 | Authorization |
NOT_FOUND | 404 | Resource |
QUOTE_CREATION_FAILED | 400 | Business Rule |