BaynPay
BaynPay
Overview
API Reference

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.com

Start 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:

  1. Remove the hex_ prefix from your API Key Secret
  2. Decode the hexadecimal string to bytes
  3. Compute HMAC-SHA256 using the decoded key and canonical string
  4. Encode the result as a hexadecimal string
  5. Include the signature in the X-Signature header

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:

ParameterTypeRequiredDescription
first_namestringYesClient's first name
last_namestringYesClient's last name
emailstringYesUnique email address
phonestringOptionalPhone number in E.164 format
client_reference_idstringYesYour internal reference (order ID, invoice number)
addressobjectYesCountry 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

StatusDescription
ACCEPTEDQuote created, deposit address assigned, awaiting payment
PAIDExact quoted amount received
OVERPAIDReceived more than quoted amount
UNDERPAIDReceived less than quoted amount
EXPIREDNo 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

StatusDescription
CREATEDTransaction initiated on blockchain
PENDINGAwaiting block confirmations and verification
COMPLETEDTransaction confirmed, funds transferred successfully
FAILEDTransaction failed due to error or rejection

HTTP Status Codes

CodeMeaning
200Success
400Bad Request - Invalid parameters
401Unauthorized - Invalid or missing API key
404Not Found - Resource doesn't exist
409Conflict - Quote already confirmed or expired
422Unprocessable Entity - Business logic error
429Rate Limited - Too many requests
500Server 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:

  1. Navigate to Developer Center → Webhook Manager
  2. Click Add Webhook Endpoint
  3. Enter your HTTPS endpoint URL
  4. Select the events you want to receive
  5. Save the webhook secret provided (required for signature verification)

Webhook Events

Baynpay sends webhooks for these transaction events:

Event TypeDescriptionWhen Triggered
transaction.createdNew transaction initiatedWhen a deposit or withdrawal is first created
transaction.status.updatedTransaction status changedWhen 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

HeaderDescription
Content-TypeAlways application/json
X-Webhook-SignatureBase64-encoded HMAC-SHA256 signature
X-Webhook-EventEvent type (e.g., transaction.created)
X-Webhook-TimestampUnix timestamp (seconds) when webhook was sent
User-AgentAlways VisionPay-Webhooks/1.0

Signature Verification

The signature is computed as:

base64(hmac_sha256(secret, "{timestamp}.{json_payload}"))

Follow these verification steps:

  1. Extract X-Webhook-Timestamp and X-Webhook-Signature headers
  2. Concatenate timestamp and raw JSON payload: {timestamp}.{payload}
  3. Compute HMAC-SHA256 using your webhook secret
  4. Base64-encode the result
  5. 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

PropertyValue
Token NameBaynPay USD Test
Token SymbolUSDB
Blockchain NetworkPolygon testnet
Contract Address0x11238A992c00E6dF615F6bbCded8198CAe84ece2

How to Use the Faucet

Access the faucet through Developer Center → Faucet Tokens in your merchant portal:

  1. Enter your Polygon wallet address (must start with 0x)
  2. Specify the token amount you need (between 1 and 10,000 tokens)
  3. 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 CodeHTTP StatusCategory
VALIDATION_ERROR400Client / Input
UNAUTHORIZED401Authentication
ACCESS_DENIED403Authorization
NOT_FOUND404Resource
QUOTE_CREATION_FAILED400Business Rule