Paychainly
API Reference
Base URL
https://api.paychainly.com

API Reference

v1

REST API for integrating USDT (BEP-20) payments into your platform. Manage customers, generate deposit addresses or payment links, and query transactions — all secured with an API key. Never expose your key in client-side code.

Authentication

Create an API key in API Keys. Pass it in every request as a header — both formats are accepted:

Bearer Token
Authorization: Bearer YOUR_API_KEY
API Key Header
X-Api-Key: YOUR_API_KEY

Integration Patterns

#PatternCustomerAddressPayment LinkUser Pays ViaBest For
1Wallet — address only✓ Required
reusepermanent, never changes
✗ Not needed
Address or QR — user sends anytimeDeposit wallets, open-ended top-ups
2Wallet — with payment link✓ Required
reusesame address every time
createForAddress()
① Redirect to payUrl ② Custom UI ③ QR codeTop-up with a specific fixed amount
3Order Checkout — logged-in user✓ Required
generate_newfresh per order
paymentLinks.create()
① Redirect to payUrl ② Custom UI ③ QR codeE-commerce, invoices, subscriptions
4Guest Checkout✗ Not needed
generate_newfresh per order
paymentLinks.create()
① Redirect to payUrl ② Custom UI ③ QR codeOne-off payments, fully anonymous

For patterns 2, 3, and 4 — all three payment options come from the same single API call. Pick whichever fits your UI:

link.payUrl    → https://paychainly.com/pay/:slug   // ① redirect to hosted page
link.address   → 0xABC...                            // ② show in your own UI
link.address   → 0xABC... (QR-encode it)             // ③ QR code
link.amount    → "49.99"
link.expiresAt → "2026-06-03T10:00:00.000Z"

Customers

8
POST/api/integration/customers

Create Customer

Creates a new customer record. The identifier is required and must be unique per account — use your internal user ID, order reference, or email. Once created, generate-address will reuse the same wallet for this customer automatically via their identifier.

Headers
Content-Typeapplication/jsonrequired
X-Api-KeyYOUR_API_KEYrequired
Body Parametersapplication/json
ParameterTypeRequiredDescription
identifierstringrequiredUnique key for this customer — e.g. your internal user ID, order ID, or email. Used by generate-address in reuse mode to return the same wallet every time.
namestringoptionalCustomer display name
emailstringoptionalCustomer email address
notestringoptionalInternal note visible only to you
metadataobjectoptionalAny custom JSON payload e.g. { "plan": "pro", "region": "EU" }
Responses
{
  "success": true,
  "data": {
    "id": 7,
    "identifier": "user_12345",
    "name": "John Doe",
    "email": "[email protected]",
    "note": "VIP customer",
    "metadata": { "plan": "pro" },
    "mode": "production",
    "createdAt": "2026-05-24T10:00:00.000Z",
    "updatedAt": "2026-05-24T10:00:00.000Z"
  }
}
curl -X POST https://api.paychainly.com/api/integration/customers \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{
    "identifier": "user_12345",
    "name": "John Doe",
    "email": "[email protected]",
    "note": "VIP customer",
    "metadata": { "plan": "pro" }
  }'
GET/api/integration/customers

List Customers

Returns a paginated list of all your customers. Search by name, email, or identifier. Useful for looking up a customer before generating a deposit address or checking payment history.

Headers
X-Api-KeyYOUR_API_KEYrequired
Query Parameters
ParameterTypeRequiredDescription
limitnumberoptionalMax results per page (max 100). Default: 20
offsetnumberoptionalNumber of results to skip. Default: 0
searchstringoptionalFull-text search across name, email, and identifier fields.
modestringoptionalproduction or sandbox
Responses
{
  "success": true,
  "data": [
    {
      "id": 7,
      "identifier": "user_12345",
      "name": "John Doe",
      "email": "[email protected]",
      "note": "VIP customer",
      "metadata": { "plan": "pro" },
      "mode": "production",
      "createdAt": "2026-05-24T10:00:00.000Z",
      "updatedAt": "2026-05-24T10:00:00.000Z"
    }
  ],
  "meta": { "total": 1, "limit": 20, "offset": 0 }
}
# List all customers
curl "https://api.paychainly.com/api/integration/customers?limit=20&offset=0" \
  -H "X-Api-Key: YOUR_API_KEY"

# Search by email
curl "https://api.paychainly.com/api/integration/[email protected]" \
  -H "X-Api-Key: YOUR_API_KEY"
GET/api/integration/customers/:customerUid

Get Customer

Get a customer by their customerUid (the UUID assigned at creation). Use get_customer_by_identifier for lookup by your own reference.

Headers
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
customerUidstring (UUID)requiredCustomer UUID assigned at creation (the customerUid field in the response, e.g. "dd8693ec-8b5f-43ef-b4e5-1d0a088df1a3")
Responses
{
  "success": true,
  "data": {
    "id": 7,
    "identifier": "user_12345",
    "name": "John Doe",
    "email": "[email protected]",
    "note": "VIP customer",
    "metadata": { "plan": "pro" },
    "mode": "production",
    "createdAt": "2026-05-24T10:00:00.000Z",
    "updatedAt": "2026-05-24T10:00:00.000Z"
  }
}
# By numeric ID
curl "https://api.paychainly.com/api/integration/customers/7" \
  -H "X-Api-Key: YOUR_API_KEY"

# By email
curl "https://api.paychainly.com/api/integration/customers/[email protected]" \
  -H "X-Api-Key: YOUR_API_KEY"

# By custom identifier
curl "https://api.paychainly.com/api/integration/customers/user_12345" \
  -H "X-Api-Key: YOUR_API_KEY"
GET/api/integration/customers/by-email/:email

Get Customer by Email

Looks up a customer by their exact email address. Returns 404 if no customer with that email exists. URL-encode the email before embedding it in the path (e.g. john%40example.com).

Headers
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
emailstringrequiredCustomer email address. URL-encode the @ symbol as %40 (e.g. john%40example.com).
Responses
{
  "success": true,
  "data": {
    "id": 7,
    "identifier": "user_12345",
    "name": "John Doe",
    "email": "[email protected]",
    "note": "VIP customer",
    "metadata": { "plan": "pro" },
    "mode": "production",
    "createdAt": "2026-05-24T10:00:00.000Z",
    "updatedAt": "2026-05-24T10:00:00.000Z"
  }
}
# URL-encode the @ as %40
curl "https://api.paychainly.com/api/integration/customers/by-email/john%40example.com" \
  -H "X-Api-Key: YOUR_API_KEY"
GET/api/integration/customers/by-identifier/:identifier

Get Customer by Identifier

Looks up a customer by their exact identifier — the unique key you set when creating the customer (e.g. your internal user ID, order reference, or any string). Returns 404 if not found.

Headers
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
identifierstringrequiredThe exact identifier value used when creating the customer (e.g. user_12345, ORD-999). URL-encode special characters if needed.
Responses
{
  "success": true,
  "data": {
    "id": 7,
    "identifier": "user_12345",
    "name": "John Doe",
    "email": "[email protected]",
    "note": "VIP customer",
    "metadata": { "plan": "pro" },
    "mode": "production",
    "createdAt": "2026-05-24T10:00:00.000Z",
    "updatedAt": "2026-05-24T10:00:00.000Z"
  }
}
curl "https://api.paychainly.com/api/integration/customers/by-identifier/user_12345" \
  -H "X-Api-Key: YOUR_API_KEY"
GET/api/integration/customers/by-deposit-address/:address

Get Customer by Deposit Address

Resolves the customer who owns a given deposit wallet address. Useful when a webhook delivers a wallet address and you need to know which of your customers it belongs to. Returns the customer record alongside the matched deposit address details.

Headers
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
addressstringrequiredFull 0x-prefixed BEP-20 wallet address (e.g. 0x1234567890abcdef...)
Responses
{
  "success": true,
  "data": {
    "customer": {
      "id": 7,
      "identifier": "user_12345",
      "name": "John Doe",
      "email": "[email protected]",
      "note": "VIP customer",
      "metadata": { "plan": "pro" },
      "mode": "production",
      "createdAt": "2026-05-24T10:00:00.000Z",
      "updatedAt": "2026-05-24T10:00:00.000Z"
    },
    "depositAddress": {
      "id": 42,
      "address": "0x1234567890abcdef1234567890abcdef12345678",
      "network": "BNB",
      "status": "active"
    }
  }
}
# From a webhook payload — find which customer owns this address
ADDR="0x1234567890abcdef1234567890abcdef12345678"

curl "https://api.paychainly.com/api/integration/customers/by-deposit-address/${ADDR}" \
  -H "X-Api-Key: YOUR_API_KEY"
PUT/api/integration/customers/by-identifier/:identifier

Update Customer by Identifier

Update a customer by their identifier. Pass the identifier in the path and only the fields to change in the body — omitted fields are left untouched.

Headers
Content-Typeapplication/jsonrequired
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
identifierstringrequiredThe exact identifier of the customer to update (e.g. user_12345).
Body Parametersapplication/json
ParameterTypeRequiredDescription
namestringoptionalUpdated display name.
emailstringoptionalUpdated email address.
identifierstringoptionalUpdated identifier value.
notestringoptionalUpdated note.
metadataobjectoptionalUpdated metadata (replaces existing).
Responses
{
  "success": true,
  "data": {
    "id": 7,
    "identifier": "user_12345",
    "name": "John Doe",
    "email": "[email protected]",
    "note": "VIP customer",
    "metadata": { "plan": "pro" },
    "mode": "production",
    "createdAt": "2026-05-24T10:00:00.000Z",
    "updatedAt": "2026-05-24T10:00:00.000Z"
  }
}
curl -X PUT "https://api.paychainly.com/api/integration/customers/by-identifier/user_12345" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{ "name": "John Doe Jr.", "note": "Upgraded to enterprise", "metadata": { "plan": "enterprise" } }'
PUT/api/integration/customers/by-email/:email

Update Customer by Email

Update a customer by their email address. URL-encode @ as %40 in the path. Only the fields included in the body are updated.

Headers
Content-Typeapplication/jsonrequired
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
emailstringrequiredCustomer email — URL-encode @ as %40 (e.g. john%40example.com).
Body Parametersapplication/json
ParameterTypeRequiredDescription
namestringoptionalUpdated display name.
emailstringoptionalUpdated email address.
identifierstringoptionalUpdated identifier value.
notestringoptionalUpdated note.
metadataobjectoptionalUpdated metadata (replaces existing).
Responses
{
  "success": true,
  "data": {
    "id": 7,
    "identifier": "user_12345",
    "name": "John Doe",
    "email": "[email protected]",
    "note": "VIP customer",
    "metadata": { "plan": "pro" },
    "mode": "production",
    "createdAt": "2026-05-24T10:00:00.000Z",
    "updatedAt": "2026-05-24T10:00:00.000Z"
  }
}
curl -X PUT "https://api.paychainly.com/api/integration/customers/by-email/john%40example.com" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{ "name": "John Doe Jr.", "metadata": { "plan": "enterprise" } }'

Deposit Addresses

5
POST/api/integration/addresses/generate

Generate Wallet

Generate a fresh deposit address without linking a customer. A new address is always created. Monitoring starts immediately — the address listens for incoming transfers the moment it is returned.

Headers
Content-Typeapplication/jsonrequired
X-Api-KeyYOUR_API_KEYrequired
Body Parametersapplication/json
ParameterTypeRequiredDescription
networkstringrequiredBlockchain network. Currently only "BNB" is supported.
tokenSymbolstringrequiredToken symbol to accept (e.g. "USDT"). Case-insensitive — stored uppercase.
notestringoptionalFree-form note attached to the deposit address.
metadataobjectoptionalArbitrary JSON metadata stored alongside the deposit address.
Responses
{
  "success": true,
  "data": {
    "id": 42,
    "address": "0x1234567890abcdef1234567890abcdef12345678",
    "status": "active",
    "mode": "production",
    "type": "wallet",
    "network": { "id": 1, "name": "BNB Smart Chain", "slug": "bnb", "chainId": 56, "nativeSymbol": "BNB" },
    "token": { "id": 2, "symbol": "USDT", "decimals": 18 },
    "customer": null,
    "note": "Payment for order #1234",
    "metadata": { "orderId": "1234" },
    "createdAt": "2026-05-24T10:00:00.000Z"
  }
}
curl -X POST "https://api.paychainly.com/api/integration/addresses/generate" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{
    "network": "BNB",
    "tokenSymbol": "USDT",
    "note": "Payment for order #1234",
    "metadata": { "orderId": "1234" }
  }'
POST/api/integration/addresses/generate

Generate Wallet with Customer

Generate (or reuse) a deposit address and link it to a customer. Pass a customer object with customerUid (lookup only), identifier, or email — if the customer does not exist it is created automatically. The response includes the full customer object.

Headers
Content-Typeapplication/jsonrequired
X-Api-KeyYOUR_API_KEYrequired
Body Parametersapplication/json
ParameterTypeRequiredDescription
networkstringrequiredBlockchain network. Currently only "BNB" is supported.
tokenSymbolstringrequiredToken symbol to accept (e.g. "USDT"). Case-insensitive.
mode"reuse"|"generate_new"optional"reuse" returns the existing active address for this customer; "generate_new" always creates a fresh one. Default: reuse
customerobjectoptionalCustomer to link. Pass one of the sub-fields below.
customer.customerUidstring (UUID)optionalLook up an existing customer by their customerUid. Returns 404 if not found.
customer.identifierstringoptionalLook up or create a customer by identifier (e.g. user_12345).
customer.emailstringoptionalLook up or create a customer by email address.
notestringoptionalFree-form note attached to the deposit address.
metadataobjectoptionalArbitrary JSON metadata stored alongside the deposit address.
Responses
{
  "success": true,
  "data": {
    "id": 42,
    "address": "0x1234567890abcdef1234567890abcdef12345678",
    "status": "active",
    "mode": "production",
    "type": "wallet",
    "network": { "id": 1, "name": "BNB Smart Chain", "slug": "bnb", "chainId": 56, "nativeSymbol": "BNB" },
    "token": { "id": 2, "symbol": "USDT", "decimals": 18 },
    "customer": {
      "id": 52,
      "customerUid": "93c7e7b7-f6e3-4708-8881-a4e0de50ab6b",
      "identifier": "user_12345",
      "name": "John Doe",
      "email": "[email protected]"
    },
    "note": "Payment for order #1234",
    "metadata": { "orderId": "1234" },
    "createdAt": "2026-05-24T10:00:00.000Z"
  }
}
curl -X POST "https://api.paychainly.com/api/integration/addresses/generate" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{
    "network": "BNB",
    "tokenSymbol": "USDT",
    "mode": "reuse",
    "customer": { "identifier": "user_12345" }
  }'
GET/api/integration/addresses

List Deposit Addresses

Returns a paginated list of all deposit addresses for your account. Filter by network, mode, or customer ID. Search matches wallet address, note, and metadata values.

Headers
X-Api-KeyYOUR_API_KEYrequired
Query Parameters
ParameterTypeRequiredDescription
limitnumberoptionalMax results per page (max 100). Default: 20
offsetnumberoptionalResults to skip. Default: 0
networkstringoptionalFilter by network e.g. BNB.
mode"production"|"sandbox"optionalFilter by deposit mode.
customerIdnumberoptionalFilter addresses linked to a specific customer ID.
searchstringoptionalSearch by wallet address, note, or metadata (JSON text match). Ignored when customerId is set.
sort"ASC"|"DESC"optionalSort by creation date. Default: DESC
Responses
{
  "success": true,
  "data": [
    {
      "id": 42,
      "address": "0x1234567890abcdef1234567890abcdef12345678",
      "status": "active",
      "mode": "production",
      "type": "wallet",
      "isDefault": false,
      "note": "Pro plan upgrade",
      "metadata": { "orderId": "1234", "plan": "pro" },
      "createdAt": "2026-05-24T10:00:00.000Z",
      "updatedAt": "2026-05-24T10:00:00.000Z",
      "network": {
        "id": 1,
        "name": "BNB Smart Chain",
        "slug": "bnb",
        "chainId": 56,
        "nativeSymbol": "BNB",
        "explorerUrl": "https://bscscan.com"
      },
      "token": {
        "id": 2,
        "symbol": "USDT",
        "displayName": "Tether USD",
        "contractAddress": "0x55d398326f99059ff775485246999027b3197955",
        "decimals": 18
      },
      "customer": {
        "id": 52,
        "customerUid": "93c7e7b7-f6e3-4708-8881-a4e0de50ab6b",
        "identifier": "user_12345",
        "name": "John Doe",
        "email": "[email protected]"
      }
    }
  ],
  "meta": { "total": 1, "limit": 20, "offset": 0, "sort": "DESC" }
}
# All addresses
curl "https://api.paychainly.com/api/integration/addresses?limit=20&offset=0" \
  -H "X-Api-Key: YOUR_API_KEY"

# Filter by customer
curl "https://api.paychainly.com/api/integration/addresses?customerId=52" \
  -H "X-Api-Key: YOUR_API_KEY"

# Search by note or metadata
curl "https://api.paychainly.com/api/integration/addresses?search=pro+plan" \
  -H "X-Api-Key: YOUR_API_KEY"
GET/api/integration/addresses/:id

Get Deposit Address

Retrieves a single deposit address. The :id segment accepts either a numeric ID or a full 0x-prefixed wallet address string — useful when you have the address from a webhook and need to fetch its full record.

Headers
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
idinteger | stringrequiredNumeric ID (e.g. 42) or full wallet address (0x1234...)
Responses
{
  "success": true,
  "data": {
    "id": 42,
    "address": "0x1234567890abcdef1234567890abcdef12345678",
    "status": "active",
    "mode": "production",
    "type": "wallet",
    "isDefault": false,
    "note": "Pro plan upgrade",
    "metadata": { "orderId": "1234", "plan": "pro" },
    "createdAt": "2026-05-24T10:00:00.000Z",
    "updatedAt": "2026-05-24T10:00:00.000Z",
    "network": {
      "id": 1,
      "name": "BNB Smart Chain",
      "slug": "bnb",
      "chainId": 56,
      "nativeSymbol": "BNB",
      "explorerUrl": "https://bscscan.com"
    },
    "token": {
      "id": 2,
      "symbol": "USDT",
      "displayName": "Tether USD",
      "contractAddress": "0x55d398326f99059ff775485246999027b3197955",
      "decimals": 18
    },
    "customer": {
      "id": 52,
      "customerUid": "93c7e7b7-f6e3-4708-8881-a4e0de50ab6b",
      "identifier": "user_12345",
      "name": "John Doe",
      "email": "[email protected]"
    }
  }
}
curl "https://api.paychainly.com/api/integration/addresses/42" \
  -H "X-Api-Key: YOUR_API_KEY"
GET/api/integration/addresses/by-address/:address

Get Wallet by Address

Retrieve full details of a wallet by its 0x wallet address string, including network, token and customer information.

Headers
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
addressstringrequiredThe 0x-prefixed wallet address (e.g. 0xabc…). URL-encode if needed.
Responses
{
  "success": true,
  "data": {
    "id": 42,
    "address": "0x1234567890abcdef1234567890abcdef12345678",
    "status": "active",
    "mode": "production",
    "type": "wallet",
    "network": { "id": 1, "name": "BNB Smart Chain", "slug": "bnb", "chainId": 56, "nativeSymbol": "BNB" },
    "token": { "id": 2, "symbol": "USDT", "decimals": 18 },
    "customer": { "id": 52, "customerUid": "93c7e7b7-…", "identifier": "user_12345", "name": "John Doe", "email": "[email protected]" },
    "createdAt": "2026-05-24T10:00:00.000Z"
  }
}
ADDR="0x1234567890abcdef1234567890abcdef12345678"
curl "https://api.paychainly.com/api/integration/addresses/by-address/${ADDR}" \
  -H "X-Api-Key: YOUR_API_KEY"

Payment Links

6

Transactions

3
GET/api/integration/transactions

List Transactions

Returns a paginated list of all USDT transfers detected for your deposit addresses. Filter by status, date range, or a specific address. Use this to reconcile payments on your side.

Headers
X-Api-KeyYOUR_API_KEYrequired
Query Parameters
ParameterTypeRequiredDescription
limitnumberoptionalMax results per page. Default: 20
offsetnumberoptionalResults to skip. Default: 0
statusstringoptionalFilter by status: pending, confirmed, swept
modestringoptionalproduction or sandbox
addressstringoptionalFilter by a specific deposit wallet address
dateFromstring (ISO 8601)optionalStart of date range (createdAt ≥ dateFrom)
dateTostring (ISO 8601)optionalEnd of date range (createdAt ≤ dateTo)
Responses
{
  "success": true,
  "data": [
    {
      "id": 101,
      "txHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
      "address": "0x1234567890abcdef1234567890abcdef12345678",
      "amount": "100.500000",
      "status": "confirmed",
      "mode": "production",
      "blockNumber": 38000000,
      "webhookSent": true,
      "createdAt": "2026-05-24T10:05:00.000Z"
    }
  ],
  "meta": { "total": 1, "limit": 20, "offset": 0 }
}
# All transactions
curl "https://api.paychainly.com/api/integration/transactions?limit=20" \
  -H "X-Api-Key: YOUR_API_KEY"

# Filter confirmed payments in May 2026
curl "https://api.paychainly.com/api/integration/transactions?status=confirmed&dateFrom=2026-05-01&dateTo=2026-05-31" \
  -H "X-Api-Key: YOUR_API_KEY"
GET/api/integration/transactions/by-address/:address

List Transactions by Address

Returns all USDT transactions received by a specific deposit wallet address, paginated and ordered by block number descending. Supports optional status and date-range filters. Useful for showing payment history for a single customer wallet.

Headers
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
addressstringrequiredFull 0x-prefixed deposit wallet address
Query Parameters
ParameterTypeRequiredDescription
limitnumberoptionalMax results per page. Default: 20
offsetnumberoptionalResults to skip. Default: 0
statusstringoptionalFilter by status: pending, confirmed, swept
dateFromstring (ISO 8601)optionalStart of date range (createdAt ≥ dateFrom)
dateTostring (ISO 8601)optionalEnd of date range (createdAt ≤ dateTo)
Responses
{
  "success": true,
  "data": [
    {
      "id": 101,
      "txHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
      "address": "0x1234567890abcdef1234567890abcdef12345678",
      "amount": "100.500000",
      "status": "confirmed",
      "mode": "production",
      "blockNumber": 38000000,
      "webhookSent": true,
      "createdAt": "2026-05-24T10:05:00.000Z"
    }
  ],
  "meta": {
    "total": 1,
    "limit": 20,
    "offset": 0,
    "address": "0x1234567890abcdef1234567890abcdef12345678"
  }
}
ADDR="0x1234567890abcdef1234567890abcdef12345678"

# All transactions for this address
curl "https://api.paychainly.com/api/integration/transactions/by-address/${ADDR}?limit=20" \
  -H "X-Api-Key: YOUR_API_KEY"

# Only confirmed, filtered by date
curl "https://api.paychainly.com/api/integration/transactions/by-address/${ADDR}?status=confirmed&dateFrom=2026-05-01" \
  -H "X-Api-Key: YOUR_API_KEY"
GET/api/integration/transactions/:id

Get Transaction

Retrieves a single transaction by numeric ID or full txHash. Useful when your webhook receives a txHash and you want to fetch the full record. Both formats are accepted.

Headers
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
idinteger | stringrequiredNumeric transaction ID (e.g. 101) or full txHash (0x...)
Responses
{
  "success": true,
  "data": {
    "id": 101,
    "txHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
    "address": "0x1234567890abcdef1234567890abcdef12345678",
    "amount": "100.500000",
    "status": "confirmed",
    "mode": "production",
    "blockNumber": 38000000,
    "webhookSent": true,
    "createdAt": "2026-05-24T10:05:00.000Z"
  }
}
# By numeric ID
curl "https://api.paychainly.com/api/integration/transactions/101" \
  -H "X-Api-Key: YOUR_API_KEY"

# By txHash (from webhook payload)
curl "https://api.paychainly.com/api/integration/transactions/0xabcdef..." \
  -H "X-Api-Key: YOUR_API_KEY"

Invoices

3
GET/api/integration/invoice/:txId

Download Invoice PDF (Universal)

Download the PDF invoice for a transaction. Accepts either a numeric transaction ID or an on-chain transaction hash — the server detects which format was passed automatically. This is the recommended endpoint.

Headers
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
txIdstring | numberrequiredEither the numeric transaction ID (e.g. "42") or the on-chain tx hash (e.g. "0xabc123...")
Responses
// Returns a PDF binary stream
// Content-Type: application/pdf
// Content-Disposition: attachment; filename="INV-42.pdf"
# By numeric ID:
curl https://api.paychainly.com/api/integration/invoice/42 \
  -H "X-Api-Key: YOUR_API_KEY" \
  --output invoice-42.pdf

# By on-chain hash:
curl https://api.paychainly.com/api/integration/invoice/0xabc123... \
  -H "X-Api-Key: YOUR_API_KEY" \
  --output invoice.pdf
GET/api/integration/invoice/by-id/:id

Download Invoice PDF by ID

Generate and download a PDF invoice for a completed transaction using its numeric ID. Returns a binary PDF stream using your Invoice Settings.

Headers
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
idintegerrequiredNumeric transaction ID (e.g. 8).
Responses
Content-Type: application/pdf
Content-Disposition: attachment; filename="invoice-8.pdf"

<binary PDF data>
curl "https://api.paychainly.com/api/integration/invoice/by-id/8" \
  -H "X-Api-Key: YOUR_API_KEY" \
  --output invoice-8.pdf
GET/api/integration/invoice/by-hash/:txHash

Download Invoice PDF by Tx Hash

Generate and download a PDF invoice for a completed transaction using the on-chain transaction hash. Returns a binary PDF stream using your Invoice Settings.

Headers
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
txHashstringrequiredOn-chain transaction hash (e.g. "0xabc123…").
Responses
Content-Type: application/pdf
Content-Disposition: attachment; filename="invoice.pdf"

<binary PDF data>
HASH="0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab"
curl "https://api.paychainly.com/api/integration/invoice/by-hash/${HASH}" \
  -H "X-Api-Key: YOUR_API_KEY" \
  --output invoice.pdf

Withdrawals

7
POST/api/integration/withdrawals

Create Withdrawal

Initiate a USDT withdrawal from your Paychainly balance to an external wallet. Always generate a unique UUID for idempotencyKey and store it before calling — submit the same key on retry to prevent duplicate withdrawals.

Headers
Content-Typeapplication/jsonrequired
X-Api-KeyYOUR_API_KEYrequired
Body Parametersapplication/json
ParameterTypeRequiredDescription
idempotencyKeystringrequiredUnique key per withdrawal request scoped to your account. Can be any string (e.g. "payout-2026-001"). Re-submit the same key on retry — returns original result, never a duplicate.
networkstringrequiredNetwork slug e.g. "BNB" for BSC
toAddressstringrequiredDestination EVM wallet address (0x...)
amountstringrequiredAmount to withdraw as decimal string e.g. "100.00"
feeMode"deduct" | "add"required"deduct" — fee taken from withdrawal amount. "add" — fee charged on top of withdrawal.
notestringoptionalInternal note for this withdrawal — visible only to you, not shown to recipient
metadataobjectoptionalCustom JSON payload e.g. { "orderId": "123", "userId": "456" }
Responses
{
  "success": true,
  "data": {
    "id": 1,
    "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
    "network": "BNB",
    "toAddress": "0xRecipientAddress",
    "amount": "100.00",
    "feeMode": "deduct",
    "status": "pending",
    "txHash": null,
    "createdAt": "2026-06-01T10:00:00.000Z"
  }
}
curl -X POST https://api.paychainly.com/api/integration/withdrawals \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{
    "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
    "network": "BNB",
    "toAddress": "0xRecipientWalletAddress",
    "amount": "100.00",
    "feeMode": "deduct",
    "note": "Monthly payout",
    "metadata": { "orderId": "order-456" }
  }'
GET/api/integration/withdrawals

List Withdrawals

List all withdrawal requests for your account with pagination.

Headers
X-Api-KeyYOUR_API_KEYrequired
Query Parameters
ParameterTypeRequiredDescription
limitnumberoptionalMax records to return (default 20, max 100) Default: 20
offsetnumberoptionalRecords to skip for pagination Default: 0
Responses
{
  "success": true,
  "data": [
    {
      "id": 1,
      "network": "BNB",
      "toAddress": "0xRecipientAddress",
      "amount": "100.00",
      "feeMode": "deduct",
      "status": "completed",
      "txHash": "0xabc123...",
      "createdAt": "2026-06-01T10:00:00.000Z"
    }
  ],
  "total": 1
}
curl https://api.paychainly.com/api/integration/withdrawals?limit=20 \
  -H "X-Api-Key: YOUR_API_KEY"
GET/api/integration/withdrawals/:id

Get Withdrawal

Get a withdrawal request by its numeric ID.

Headers
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
idnumberrequiredWithdrawal numeric ID
Responses
{
  "success": true,
  "data": {
    "id": 1,
    "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
    "network": "BNB",
    "toAddress": "0xRecipientAddress",
    "amount": "100.00",
    "feeMode": "deduct",
    "status": "completed",
    "txHash": "0xabc123...",
    "createdAt": "2026-06-01T10:00:00.000Z",
    "updatedAt": "2026-06-01T10:01:00.000Z"
  }
}
curl https://api.paychainly.com/api/integration/withdrawals/1 \
  -H "X-Api-Key: YOUR_API_KEY"
POST/api/integration/withdrawals/:id/cancel

Cancel Withdrawal

Cancel a pending withdrawal before it is processed. Only withdrawals with status "pending" can be cancelled.

Headers
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
idnumberrequiredWithdrawal numeric ID to cancel
Responses
{
  "success": true,
  "data": {
    "id": 1,
    "status": "cancelled",
    "updatedAt": "2026-06-01T10:02:00.000Z"
  }
}
curl -X POST https://api.paychainly.com/api/integration/withdrawals/1/cancel \
  -H "X-Api-Key: YOUR_API_KEY"
GET/api/integration/withdrawals/by-address/:address

Get Withdrawals by Address

Get all withdrawal requests that were sent to a specific destination wallet address.

Headers
X-Api-KeyYOUR_API_KEYrequired
Path Parameters
ParameterTypeRequiredDescription
addressstringrequiredDestination wallet address to filter by (0x...)
Query Parameters
ParameterTypeRequiredDescription
pagenumberoptionalPage number (default 1) Default: 1
limitnumberoptionalRecords per page (default 20) Default: 20
statusstringoptionalFilter by status: "pending", "processing", "completed", "failed", "cancelled"
Responses
{
  "success": true,
  "data": [
    {
      "id": 1,
      "toAddress": "0xRecipientAddress",
      "network": "BNB",
      "amount": "100.00",
      "status": "completed",
      "txHash": "0xabc123...",
      "createdAt": "2026-06-01T10:00:00.000Z"
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 20
}
curl "https://api.paychainly.com/api/integration/withdrawals/by-address/0xRecipientAddress?page=1&limit=20" \
  -H "X-Api-Key: YOUR_API_KEY"
GET/api/integration/withdrawals/wallet

Get Withdraw Wallet

Returns the withdraw wallet(s) configured for your account. Optionally filter by network.

Headers
X-Api-KeyYOUR_API_KEYrequired
Query Parameters
ParameterTypeRequiredDescription
networkstringoptionalFilter by network slug e.g. "BNB"
Responses
{
  "success": true,
  "data": [
    {
      "network": "BNB",
      "address": "0xYourWithdrawWalletAddress",
      "source": "generated",
      "mode": "production"
    }
  ]
}
curl https://api.paychainly.com/api/integration/withdrawals/wallet \
  -H "X-Api-Key: YOUR_API_KEY"
GET/api/integration/withdrawals/wallet-balance

Get Withdraw Wallet Balance

Returns the live on-chain USDT and native token (BNB) balance of your withdraw wallet(s). Balances are fetched in real-time from the blockchain. Optionally filter by network.

Headers
X-Api-KeyYOUR_API_KEYrequired
Query Parameters
ParameterTypeRequiredDescription
networkstringoptionalFilter by network slug e.g. "BNB"
Responses
{
  "success": true,
  "data": [
    {
      "network": "BNB",
      "address": "0xYourWithdrawWalletAddress",
      "source": "generated",
      "mode": "production",
      "usdtBalance": "245.500000000000000000",
      "nativeBalance": "0.012300000000000000"
    }
  ]
}
curl https://api.paychainly.com/api/integration/withdrawals/wallet-balance \
  -H "X-Api-Key: YOUR_API_KEY"

Sandbox

1
POST/api/sandbox/credit

Credit Test Funds

Credit test USDT to a sandbox deposit address to simulate a payment. Only works in sandbox mode — no real funds involved. Use this to test your integration end-to-end without sending real crypto.

Headers
Content-Typeapplication/jsonrequired
X-Api-KeyYOUR_API_KEYrequired
Body Parametersapplication/json
ParameterTypeRequiredDescription
addressstringrequiredSandbox deposit address to credit (0x...)
amountstringrequiredAmount of test USDT to credit e.g. "100"
Responses
{
  "success": true,
  "data": {
    "address": "0xYourSandboxDepositAddress",
    "amount": "100",
    "txHash": "0xsimulated_tx_hash",
    "status": "simulated"
  }
}
curl -X POST https://api.paychainly.com/api/sandbox/credit \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{
    "address": "0xYourSandboxDepositAddress",
    "amount": "100"
  }'

Error Codes

All error responses follow the same envelope. The code field is machine-readable and stable — use it in your error handling logic.

StatusCode
401
API_KEY_REQUIREDNo API key was provided in the request
401
INVALID_API_KEYThe API key is invalid, expired, or revoked
403
FORBIDDENThe key does not have permission for this resource
404
NOT_FOUNDThe requested resource does not exist
422
VALIDATION_ERROROne or more request fields failed validation
500
INTERNAL_ERRORUnexpected server-side error
Error Response
{
  "success": false,
  "error": {
    "code": "INVALID_API_KEY",
    "message": "Invalid or missing API key"
  }
}
Package
@paychainly/sdk

Node.js SDK

TypeScript SDK for the Paychainly platform. Accept USDT on BNB Smart Chain — manage customers, deposit addresses, payment links, invoices, and withdrawals without writing raw HTTP calls.

Installation

npm install @paychainly/sdk
# .env
PAYCHAINLY_API_KEY=pk_live_your_key_here
WEBHOOK_SECRET=your_webhook_secret

Setup

Create one instance and export it — import everywhere else.

// lib/paychainly.ts
import { Paychainly } from '@paychainly/sdk';

export const paychainly = new Paychainly({
  apiKey:     process.env.PAYCHAINLY_API_KEY!,
  baseUrl:    'https://api.paychainly.com', // optional
  timeout:    30_000,                        // optional — default 30s
  retries:    3,                             // optional — retry on 5xx
  retryDelay: 500,                           // optional — ms, doubles per attempt
});

Integration Patterns

#PatternCustomerAddressPayment LinkBest For
1Wallet — address onlyRequiredreuse — permanentNot neededDeposit wallets, top-ups
2Wallet — with linkRequiredreuse — samecreateForAddress()Fixed-amount top-ups
3Order Checkout (logged in)Requiredgenerate_newpaymentLinks.create()E-commerce, invoices
4Guest CheckoutNot neededgenerate_newpaymentLinks.create()One-off, anonymous

Pattern 1 — Permanent Wallet

Each user gets one permanent deposit address. Same address every time — safe to call on every login.

import { Paychainly, ApiError } from '@paychainly/sdk';

// Step 1 — get or create customer (handles 409 duplicate automatically)
async function getOrCreateCustomer(userId: string, email?: string) {
  try {
    return await paychainly.customers.create({ identifier: userId, email });
  } catch (err) {
    if (err instanceof ApiError && err.status === 409)
      return await paychainly.customers.getByIdentifier(userId);
    throw err;
  }
}

// Step 2 — get permanent wallet address (same every time for this customer)
const customer = await getOrCreateCustomer('user_abc123', '[email protected]');
const wallet   = await paychainly.addresses.generate({
  tokenSymbol: 'USDT',
  network:     'BNB',
  mode:        'reuse',             // ← always returns same address
  customer:    { identifier: 'user_abc123' },
});

console.log(wallet.address);       // 0xABC... — show this to the user

Pattern 2 — Wallet + Payment Link

Same permanent address, wrapped in a hosted checkout page with a fixed amount and expiry.

// Get the customer's permanent wallet
const wallet = await paychainly.addresses.generate({
  tokenSymbol: 'USDT', network: 'BNB', mode: 'reuse',
  customer: { identifier: 'user_abc123' },
});

// Wrap it in a payment link (amount + expiry)
const link = await paychainly.paymentLinks.createForAddress(wallet.address, {
  amount:      '50.00',
  memo:        'Add $50 to balance',
  expiryHours: 24,
});

// All 3 options from one call:
link.payUrl    // → https://paychainly.com/pay/pl_xxx  (Option 1: redirect)
link.address   // → 0xABC...                            (Option 2: custom UI / QR)

Pattern 3 — Order Checkout

Each order gets a fresh deposit address. uniqueId is an idempotency key — safe to retry.

const link = await paychainly.paymentLinks.create({
  uniqueId:    'order_001',          // idempotency key
  tokenSymbol: 'USDT',
  network:     'BNB',
  amount:      '49.99',
  memo:        'Premium Plan',
  expiryHours: 24,
  customer:    { identifier: 'user_abc123' },
  metadata:    { orderId: 'order_001' },
});

link.payUrl   // → hosted checkout page
link.address  // → deposit address for this order

Pattern 4 — Guest Checkout

No customer required. Anyone can pay — fully anonymous.

const link = await paychainly.paymentLinks.create({
  uniqueId:    'guest_order_002',
  tokenSymbol: 'USDT',
  network:     'BNB',
  amount:      '29.99',
  expiryHours: 24,
  // no customer field — anonymous checkout
});

link.payUrl   // redirect the user here

Receiving Payments

Set your webhook URL in the dashboard. Use express.raw() — not express.json() — so signature verification works.

import express from 'express';
import { Paychainly } from '@paychainly/sdk';

app.post('/webhooks', express.raw({ type: '*/*' }), (req, res) => {
  let event;
  try {
    event = Paychainly.webhooks.verify(
      req.body,
      req.headers['x-paychainly-signature'] as string,
      process.env.WEBHOOK_SECRET!,
    );
  } catch { return res.sendStatus(400); }

  if (event.event === 'deposit_detected') {
    // Fires within seconds — use this to notify your user immediately
    console.log(`Detected: +${event.data.amount} USDT · tx ${event.data.txHash}`);
    console.log(`From: ${event.data.fromAddress}${event.data.toAddress}`);
  }

  if (event.event === 'deposit_settled') {
    // Fires after the sweep completes — includes net amount and full fee breakdown
    const { netAmount, fees } = event.data.settlement!;
    console.log(`Settled: ${netAmount} USDT net (fee: ${fees.totalFee} USDT)`);
    // Use netAmount for final reconciliation and fulfilment
  }

  res.sendStatus(200); // always 200 — prevents retries
});

Testing signatures without a real payment:

// Generate a test signature (Node.js)
const sig = Paychainly.webhooks.sign(payload, process.env.WEBHOOK_SECRET!);

// Verify in browser / edge runtimes (no node:crypto needed)
const event = await Paychainly.webhooks.verifyBrowser(body, sig, secret);

What's new in 1.0.14

Retry with exponential backoff

new Paychainly({ apiKey, retries: 3, retryDelay: 500 })
// Retries on 5xx — not on 4xx. Delay doubles per attempt.

Per-call timeout

await paychainly.transactions.list({ limit: 10 }, { timeout: 5000 })

Auto-pagination

const all = await paychainly.customers.listAll();
// Works on: customers, addresses, transactions, paymentLinks, withdrawals

Extended transaction filters

await paychainly.transactions.list({
  dateFrom: '2026-01-01', dateTo: '2026-06-30',
  minAmount: '10.00',    maxAmount: '1000.00',
  status: 'swept',
})

Revoke by wallet address

await paychainly.addresses.revokeByAddress('0xABC...');
// No need to look up the numeric ID first

Sandbox credit

await paychainly.sandbox.credit('0xDEPOSIT...', '50.00');
// Triggers full payment flow (webhook + sweep) without real USDT

API Reference

paychainly.customers
· create(params)
· list(opts?)
· listAll(opts?)
· get(id)
· getByIdentifier(id)
· getByEmail(email)
· getByUid(uid)
· getByDepositAddress(addr)
· updateByIdentifier(id, data)
· updateByEmail(email, data)
paychainly.addresses
· generate(params)
· list(opts?)
· listAll(opts?)
· get(id)
· getByAddress(addr)
· revoke(id)
· revokeByAddress(addr)
paychainly.transactions
· list(opts?)
· listAll(opts?)
· listByAddress(addr, opts?)
· get(id)
· getByHash(txHash)
paychainly.paymentLinks
· create(params)
· list(opts?)
· listAll(opts?)
· get(id)
· getBySlug(slug)
· getByAddress(addr)
· getByUniqueId(uid)
· createForAddress(addr, params?)
paychainly.withdrawals
· create(params)
· list(opts?)
· listAll(opts?)
· get(id)
· cancel(id)
· listByAddress(addr, opts?)
paychainly.sandbox
· credit(address, amount)
paychainly.webhooks (static)
· verify(body, sig, secret)
· sign(payload, secret)
· verifyBrowser(body, sig, secret)
Package
@paychainly/react

React Package

React hooks and components for Paychainly. Accept USDT payments with real-time detection — no API key in the browser, ever.

Installation

npm install @paychainly/react

How it works — no API key in browser

Your server creates payment links using @paychainly/sdk (API key stays server-side). It returns only the slug to your frontend. @paychainly/react fetches public data and listens for real-time events — no API key needed.

// Your server (Node.js + @paychainly/sdk)
const link = await paychainly.paymentLinks.create({ ... });
return { slug: link.slug };   // ← only this reaches the browser

// Your frontend (@paychainly/react — no API key)
usePaymentLink(slug)      // GET /api/public/payment-links/:slug — no auth
usePaymentStatus(address) // Socket.io subscribe — no auth

Quick Start

Full checkout in 10 lines — pass a slug from your server.

import { PaymentCheckout } from '@paychainly/react';

function OrderPage({ slug }: { slug: string }) {
  return (
    <PaymentCheckout
      slug={slug}
      onPaymentConfirmed={({ txHash, amount }) => {
        console.log('Paid!', amount, 'USDT —', txHash);
      }}
    />
  );
}

<PaymentCheckout />

Full checkout from a single slug. Handles loading, QR, copy, countdown, confirmed, and expired states automatically.

<PaymentCheckout
  slug="pl_abc123"
  apiUrl="https://api.paychainly.com"     // optional
  socketUrl="https://api.paychainly.com"  // optional

  // Replace the default spinner
  loadingComponent={<MySpinner />}

  // Inject CSS variables on container
  injectCssVars  // → --pc-primary, --pc-bg, --pc-surface, etc.

  // Custom icons for copy button
  copyIcon={<ClipboardIcon />}
  copiedIcon={<CheckIcon />}

  // Theme
  theme={{ colors: { primary: '#6366f1', background: '#0f0f0f' } }}

  // classNames override per element
  classNames={{ container: 'my-checkout', qr: 'my-qr' }}

  // Callbacks
  onPaymentConfirmed={({ txHash, amount, fromAddress }) => {}}
  onExpired={() => {}}
/>

classNames slots

SlotApplied to
containerouter div (active state)
headermemo text
qrQR code wrapper
addressAddressCopy wrapper
amountamount display
countdowncountdown badge
confirmedBannerconfirmed state div
expiredBannerexpired state div
errorBannererror state div
loadingContainerloading state div

<WalletAddressView />

Permanent wallet display — no API calls, no Socket.io. Pass the address directly.

<WalletAddressView
  address="0xABC..."
  network="BNB Smart Chain"
  tokenSymbol="USDT"
  amount="50.00"              // optional — shows amount above QR
  label="Your deposit address"
  theme={{ colors: { primary: '#6366f1' } }}
  classNames={{ container: 'my-wallet' }}
  copyIcon={<ClipboardIcon />}
  copiedIcon={<CheckIcon />}
/>

<PaymentStatus />

Minimal badge — colored dot + label. Embeds in order pages, tables, dashboards.

// Default: colored dot + "active" / "confirmed" / "expired" / "loading"
<PaymentStatus slug="pl_abc123" />

// Custom renderer — full control over the markup
<PaymentStatus
  slug="pl_abc123"
  renderStatus={status => (
    <span className={`badge badge-${status}`}>{status}</span>
  )}
/>
loading
active
confirmed
expired
error

Hooks

usePaymentLink(slug, opts?)

Fetches public link data — address, amount, memo, status, expiresAt. No API key needed.

const { data, loading, error, refetch } = usePaymentLink('pl_abc123', {
  apiUrl: 'https://api.paychainly.com', // optional
});
// data: { address, network, tokenSymbol, amount, memo, status, expiresAt }
usePaymentStatus(address, opts?)

Connects Socket.io and listens for payment_confirmed. SSR-safe.

const { confirmed, txHash, amount, fromAddress } = usePaymentStatus('0xABC...', {
  socketUrl: 'https://api.paychainly.com', // optional
});
useCountdown(expiresAt, opts?)

Live countdown from an ISO 8601 expiry string. Three display formats.

const { formatted, expired, ms } = useCountdown('2026-12-31T23:59:59Z', {
  format: 'timer',    // "14:32"         (default)
  // format: 'relative', // "in 14 minutes"
  // format: 'absolute', // "12/31/2026, 11:59 PM"
});
useTransactionHistory(address, opts?)

Fetches transactions for an address + subscribes to Socket.io live_tx for real-time updates.

const { transactions, loading, error, refetch } = useTransactionHistory('0xABC...', {
  apiUrl:    'https://api.paychainly.com',
  socketUrl: 'https://api.paychainly.com',
  limit:     20,
});
// New transactions are prepended automatically via live_tx socket event

Primitives

Unstyled building blocks for fully custom UIs.

import { AddressQR, AddressCopy } from '@paychainly/react';

// QR code — uses qrcode.react internally
<AddressQR
  address="0xABC..."
  size={200}
  fgColor="#6366f1"         // QR module colour
  bgColor="#1a1a1a"         // QR background colour
  className="my-qr"
/>

// Address display + copy button
<AddressCopy
  address="0xABC..."
  truncate={true}           // default true — shows 0x1234...cdef
  showFullToggle            // adds expand/collapse button
  iconColor="#ffffff"       // explicit colour — browser UA overrides inherit on <button>
  copyIcon={<MyIcon />}     // custom copy icon
  copiedIcon={<MyCheck />}  // custom copied icon
  onCopied={(addr) => analyticsTrack('address_copied', addr)}
  className="my-copy"
/>

Theming

Pass a theme prop to any full component. All colors, border radius, and fonts are configurable.

const darkTheme = {
  colors: {
    primary:    '#06b6d4',   // QR code colour + accent
    background: '#0f0f0f',   // card background
    surface:    '#1a1a1a',   // input / badge backgrounds
    text:       '#ffffff',   // primary text
    textMuted:  '#888888',   // secondary text
    border:     '#2a2a2a',   // borders
    success:    '#22c55e',   // confirmed state
    error:      '#ef4444',   // expired / error state
    qrFg:       '#06b6d4',   // override QR foreground independently
    qrBg:       '#1a1a1a',   // override QR background independently
  },
  borderRadius: '16px',
  fontFamily:   'Inter, system-ui, sans-serif',
};

// Or use CSS variables (injectCssVars on PaymentCheckout)
<PaymentCheckout slug="x" injectCssVars />
// Now use: var(--pc-primary), var(--pc-bg), var(--pc-text), etc.

MetaMask / Web3 — useWalletPay

Trigger a direct USDT transfer from the user's MetaMask wallet — no copy-paste needed. Tracks every step of the transaction.

import { useWalletPay } from '@paychainly/react';

function PayWithMetaMask({ toAddress, amount, contractAddress }) {
  const { available, step, txHash, error, pay, reset } = useWalletPay({
    toAddress,
    amount,          // USDT decimal string e.g. "49.99"
    contractAddress, // USDT contract on BSC: 0x55d398326f99059fF775485246999027B3197955
    chainId: 56,     // BSC mainnet (default)
    decimals: 18,    // USDT decimals (default)
  });

  if (!available) return <p>MetaMask not detected</p>;

  const labels = {
    idle:      'Pay with MetaMask',
    connecting:'Connecting wallet…',
    switching: 'Switching network…',
    approving: 'Approve in MetaMask…',
    sending:   'Sending transaction…',
    sent:      `Sent! TX: ${txHash?.slice(0,10)}`,
    error:     `Error: ${error}`,
  };

  return (
    <button onClick={step === 'idle' ? pay : reset} disabled={!['idle','sent','error'].includes(step)}>
      {labels[step]}
    </button>
  );
}
Package
paychainly

Python SDK

Python SDK for the Paychainly platform. Accept USDT on BNB Smart Chain — sync and async clients, typed dataclass models, HMAC webhook verification. Works with Flask, FastAPI, Django, and plain scripts.

Installation

pip install paychainly
# .env
PAYCHAINLY_API_KEY=pk_live_your_key_here
WEBHOOK_SECRET=your_webhook_secret

Requires Python 3.10+. Only dependency: httpx.

Setup — Sync (Flask / Django / scripts)

Create one instance and import it everywhere. Use Paychainly for sync apps.

from paychainly import Paychainly

client = Paychainly(
    api_key="pk_live_...",               # required
    base_url="https://api.paychainly.com",  # default
    timeout=30.0,                        # seconds
    retries=3,                           # retry on 5xx — doubles per attempt
    retry_delay=0.5,                     # base delay in seconds
)

Context manager (recommended — ensures connection pool cleanup):

with Paychainly(api_key="pk_live_...") as client:
    link = client.payment_links.create(
        unique_id="order_123",
        token_symbol="USDT",
        network="BNB",
        amount="49.99",
    )

Setup — Async (FastAPI / async Django)

Use AsyncPaychainly in async frameworks. Identical API — every method is awaitable.

from paychainly import AsyncPaychainly

# FastAPI example
from fastapi import FastAPI
app = FastAPI()

client = AsyncPaychainly(api_key="pk_live_...")

@app.post("/checkout")
async def create_checkout(order_id: str, amount: str, user_id: str):
    link = await client.payment_links.create(
        unique_id=order_id,
        token_symbol="USDT",
        network="BNB",
        amount=amount,
        customer={"identifier": user_id},
    )
    return {"payUrl": link.pay_url, "address": link.address}

Async context manager:

async with AsyncPaychainly(api_key="pk_live_...") as client:
    customer = await client.customers.create(identifier="user_123")

Integration Patterns

#PatternCustomerAddressPayment LinkBest For
1Wallet — address onlyRequiredreuse — permanentNot neededDeposit wallets, top-ups
2Wallet — with linkRequiredreuse — samecreate_for_address()Fixed-amount top-ups
3Order Checkout (logged in)Requiredgenerate_newpayment_links.create()E-commerce, invoices
4Guest CheckoutNot neededgenerate_newpayment_links.create()One-off, anonymous

Pattern 1 — Permanent Wallet

Each user gets one permanent deposit address. Same address every time — safe to call on every login.

from paychainly import Paychainly, ApiError

client = Paychainly(api_key="pk_live_...")

# Step 1 — get or create customer (handles 409 duplicate automatically)
def get_or_create_customer(user_id: str, email: str = None):
    try:
        return client.customers.create(identifier=user_id, email=email)
    except ApiError as err:
        if err.status == 409:
            return client.customers.get_by_identifier(user_id)
        raise

# Step 2 — get permanent wallet address (same every time for this customer)
customer = get_or_create_customer("user_abc123", "[email protected]")
wallet = client.addresses.generate(
    token_symbol="USDT",
    network="BNB",
    mode="reuse",            # ← always returns same address
    customer={"identifier": "user_abc123"},
)

print(wallet.address)       # 0xABC... — show this to the user

Pattern 2 — Wallet + Payment Link

Same permanent address, wrapped in a hosted checkout page with a fixed amount and expiry.

# Get the customer's permanent wallet
wallet = client.addresses.generate(
    token_symbol="USDT", network="BNB", mode="reuse",
    customer={"identifier": "user_abc123"},
)

# Wrap it in a payment link (amount + expiry)
link = client.payment_links.create_for_address(
    wallet.address,
    amount="50.00",
    memo="Add $50 to balance",
    expiry_hours=24,
)

# All 3 options from one call:
link.pay_url   # → https://paychainly.com/pay/pl_xxx  (Option 1: redirect)
link.address   # → 0xABC...                            (Option 2: custom UI / QR)

Pattern 3 — Order Checkout

Each order gets a fresh deposit address. unique_id is an idempotency key — safe to retry.

link = client.payment_links.create(
    unique_id="order_001",       # idempotency key
    token_symbol="USDT",
    network="BNB",
    amount="49.99",
    memo="Premium Plan",
    expiry_hours=24,
    customer={"identifier": "user_abc123"},
    metadata={"order_id": "order_001"},
)

link.pay_url   # → hosted checkout page
link.address   # → deposit address for this order

Pattern 4 — Guest Checkout

No customer required. Anyone can pay — fully anonymous.

link = client.payment_links.create(
    unique_id="guest_order_002",
    token_symbol="USDT",
    network="BNB",
    amount="29.99",
    expiry_hours=24,
    # no customer= field — anonymous checkout
)

link.pay_url   # redirect the user here

Receiving Payments

Set your webhook URL in the dashboard. Read the raw request body before any JSON parsing — signature verification requires the unmodified bytes.

Flask:

from flask import request, abort
from paychainly import Webhooks, WebhookSignatureError

@app.post("/webhooks")
def paychainly_webhook():
    raw_body  = request.get_data()       # raw bytes — before JSON parsing
    signature = request.headers.get("X-Paychainly-Signature", "")

    try:
        event = Webhooks.verify(raw_body, signature, WEBHOOK_SECRET)
    except WebhookSignatureError:
        abort(400)

    if event.event == "deposit_detected":
        print(f"+{event.amount} USDT from {event.from_address}")
        # credit user balance, fulfil order, etc.

    return "", 200   # always 200 — prevents retries

FastAPI (async):

from fastapi import Request
from paychainly import Webhooks, WebhookSignatureError

@app.post("/webhooks")
async def paychainly_webhook(request: Request):
    raw_body  = await request.body()     # raw bytes
    signature = request.headers.get("x-paychainly-signature", "")

    try:
        event = Webhooks.verify(raw_body, signature, WEBHOOK_SECRET)
    except WebhookSignatureError:
        raise HTTPException(status_code=400)

    if event.event == "deposit_detected":
        print(f"+{event.amount} USDT tx={event.tx_hash}")

    return Response(status_code=200)

Testing signatures without a real payment:

from paychainly import Webhooks

# Generate a test signature
sig = Webhooks.sign(payload_dict, WEBHOOK_SECRET)

# Verify it
event = Webhooks.verify(json.dumps(payload_dict).encode(), sig, WEBHOOK_SECRET)
print(event.event, event.amount)

API Reference

All methods are available on both Paychainly (sync) and AsyncPaychainly (async). Async versions return coroutines — prefix with await.
client.customers
· create(identifier, ...)
· list(...)
· list_all(...)
· get(id)
· get_by_identifier(identifier)
· get_by_email(email)
· get_by_uid(customer_uid)
· get_by_deposit_address(address)
· update_by_identifier(identifier, ...)
· update_by_email(email, ...)
client.addresses
· generate(token_symbol, network, ...)
· list(...)
· list_all(...)
· get(id)
· get_by_address(address)
· revoke(id)
· revoke_by_address(address)
client.transactions
· list(...)
· list_all(...)
· list_by_address(address, ...)
· get(id)
· get_by_hash(tx_hash)
client.payment_links
· create(unique_id, token_symbol, network, ...)
· list(...)
· list_all(...)
· get(id)
· get_by_slug(slug)
· get_by_address(address)
· get_by_unique_id(unique_id)
· create_for_address(address, ...)
client.withdrawals
· create(idempotency_key, network, to_address, amount, fee_mode, ...)
· list(...)
· list_all(...)
· get(id)
· cancel(id)
· list_by_address(address, ...)
client.sandbox
· credit(address, amount)
client.system
· health()
client.Webhooks (static class)
· Webhooks.verify(raw_body, signature, secret)
· Webhooks.sign(payload, secret)
Package
@paychainly/cli
Latest
v1.1.3
@paychainly/cliv1.1.3

Relay live Paychainly webhooks to your local server during development — no public URL, no tunneling tool required. Inspect payloads, verify signatures, capture events to disk, and replay them against your handler any time.

Installation

Install globally — one command, no config file needed:

npm install -g @paychainly/cli

Verify the install:

paychainly -v
paychainly help

listen — relay webhooks

Connects to api.paychainly.com over a persistent WebSocket and forwards every webhook event it receives to your local server in real time.

paychainly listen --api-key pk_live_... --forward-to http://localhost:3000/webhook
FlagDefaultDescription
--api-keyrequiredYour Paychainly API key (pk_live_... or pk_test_...)
--forward-tostringlocalhost:3000/webhookFull URL of your local webhook endpoint
--portnumber3000Shorthand — sets forward-to to http://localhost:<port>/webhook
--hoststringhttps://api.paychainly.comOverride the Paychainly server URL
--secretstringWebhook signing secret — enables HMAC-SHA256 signature verification on every event
--verboseflagPretty-print the full JSON payload for each event
--filterstringOnly relay events matching this name, e.g. deposit_detected
--logfilepathAppend every received event to a JSONL file (one JSON per line)

Examples

# Basic — forward to port 3000
paychainly listen --api-key pk_live_... --port 3000

# Show full payload for every event
paychainly listen --api-key pk_live_... --verbose

# Only relay deposit events + save to disk for later replay
paychainly listen --api-key pk_live_... \
  --filter deposit_detected \
  --log deposits.jsonl

# Verify webhook signatures
paychainly listen --api-key pk_live_... --secret whsec_...

Press Ctrl+C to stop. A per-event-type summary is printed on exit.

status — check API key

Validates your API key and confirms the server is reachable. Prints your account email and mode. Exits with code 0 on success, 1 on failure.

paychainly status --api-key pk_live_...

Example output:

  ✓ API key valid
  Email   : [email protected]
  User ID : 42
  Mode    : production

replay — resend saved events

Reads a JSONL file produced by listen --log and replays each event to your local server. Useful after fixing a bug in your webhook handler — no real payment needed.

paychainly replay --log events.jsonl --forward-to http://localhost:3000/webhook
FlagDefaultDescription
--logrequiredPath to the JSONL file produced by listen --log
--forward-tostringlocalhost:3000/webhookLocal URL to POST events to
--filterstringOnly replay events matching this name
--delayms500Milliseconds to wait between replayed events
--verboseflagPretty-print each payload before sending

Examples

# Replay all saved events
paychainly replay --log events.jsonl

# Only replay deposits, one per second, show payloads
paychainly replay --log events.jsonl \
  --filter deposit_detected \
  --delay 1000 \
  --verbose

How relay forwarding works

The CLI connects to api.paychainly.com over a persistent WebSocket. When a payment event fires, the server checks whether the CLI is connected and routes accordingly. You do not need a webhook configured in your account for the CLI to receive events.

ScenarioWebhook configured?CLI connected?Result
1 — CLI onlyNoneYes✅ Event delivered to CLI → forwarded to --forward-to URL
2 — localhost webhook + CLIYes — localhost URLYes✅ Event delivered through CLI relay (single delivery, no double-send)
3 — public webhook + CLIYes — public HTTPS URLYes✅ Webhook POSTed to public URL AND CLI relay also receives the event
4 — public webhook, no CLIYes — public HTTPS URLNo✅ Webhook POSTed to public URL only
5 — nothing configuredNoneNo⚠️ Event fires on-chain but nothing receives it — configure a webhook or connect the CLI

Key rules

  • No webhook required — the CLI relay works independently of any webhook URL you have saved in your account.
  • No double-send for localhost webhooks — if your saved webhook URL is a localhost address, the server routes through the CLI relay and does not send it a second time.
  • Public webhook + CLI = both receive — the public URL is called via HTTP and the CLI relay also gets the event so you can inspect it locally.
  • Relay uses your --forward-to flag — the server passes an empty target URL for relay-only deliveries; the CLI falls back to the value you set with --forward-to.

Covered events: deposit_detected, sweep_started/completed/failed/skipped, address_created, withdrawal_*, manual_drain_*

Environment variables

Set these in your shell profile or .env to avoid passing flags every session:

export PAYCHAINLY_API_KEY=pk_live_...
export PAYCHAINLY_HOST=https://api.paychainly.com   # optional
export PAYCHAINLY_WEBHOOK_SECRET=whsec_...           # optional

Developer workflow

Typical dev cycle

# 1. Start your local webhook handler
node server.js   # or npm run dev

# 2. In a second terminal — start the relay and capture events
paychainly listen \
  --api-key pk_live_... \
  --log events.jsonl \
  --verbose

# 3. Trigger a test payment from the Paychainly dashboard
#    → webhook appears in your terminal, gets forwarded to your handler

# 4. Fix a bug in your handler, restart it

# 5. Replay the exact same events — no new payment needed
paychainly replay \
  --log events.jsonl \
  --filter deposit_detected

Use sandbox mode for free end-to-end testing

# Use a pk_test_... API key — no real funds involved
paychainly listen --api-key pk_test_...

# Credit test USDT to a sandbox deposit address
curl -X POST https://api.paychainly.com/api/sandbox/credit \
  -H "X-Api-Key: pk_test_..." \
  -d '{"address":"0xYOUR_ADDRESS","amount":"50"}'

Webhook event types

EventTriggered when
deposit_detectedA USDT transfer to a monitored deposit address was confirmed on-chain
sweep_completedFunds were swept from the deposit address to your master wallet
address_expiredA deposit address session expired without receiving a payment
withdrawal_completedA user-initiated USDT withdrawal was processed successfully

Use --filter deposit_detected during development to focus on the payment event and reduce noise.

Events
POST your-url

Paychainly sends a signed POST request to your registered webhook URL whenever a key event occurs. All requests use Content-Type: application/json.

deposit_detected

deposit_detected

Fired as soon as a USDT transfer to one of your deposit addresses is confirmed on-chain. Use this event to credit the user's balance immediately. The payload includes full customer and payment-link context when available.

{
  "event": "deposit_detected",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "signature": "a3f9c2...",
  "data": {
    "txHash": "0xabc123...",
    "amount": "50.00",
    "fromAddress": "0xSenderAddress...",
    "toAddress": "0xDepositAddress...",
    "address": "0xDepositAddress...",
    "addressId": 42,
    "addressType": "one_time",
    "blockNumber": 34500000,
    "transactionId": 101,
    "userId": 7,
    "mode": "production",
    "network": "BNB Smart Chain",
    "chainId": 56,
    "tokenSymbol": "USDT",
    "explorerUrl": "https://bscscan.com",
    "explorerTxUrl": "https://bscscan.com/tx/0xabc123...",
    "note": null,
    "metadata": null,
    "customer": {
      "id": 1,
      "customerUid": "cust_001",
      "name": "Alice",
      "email": "[email protected]"
    },
    "paymentLink": null
  }
}

Signing string: event|txHash|fromAddress|toAddress|amount|blockNumber|timestamp|userId

deposit_settled

deposit_settled

Fired after the full sweep pipeline completes — gas top-up, USDT transfer to your master wallet, and fee deduction. Contains everything from deposit_detected plus a complete fee breakdown and net amount credited to you.

{
  "event": "deposit_settled",
  "timestamp": "2024-01-15T10:32:10.000Z",
  "signature": "b7d4e1...",
  "data": {
    "txHash": "0xabc123...",
    "amount": "50.00",
    "fromAddress": "0xSenderAddress...",
    "toAddress": "0xDepositAddress...",
    "address": "0xDepositAddress...",
    "addressId": 42,
    "addressType": "one_time",
    "blockNumber": 34500000,
    "transactionId": 101,
    "userId": 7,
    "mode": "production",
    "network": "BNB Smart Chain",
    "chainId": 56,
    "tokenSymbol": "USDT",
    "explorerUrl": "https://bscscan.com",
    "explorerTxUrl": "https://bscscan.com/tx/0xabc123...",
    "note": null,
    "metadata": null,
    "customer": {
      "id": 1,
      "customerUid": "cust_001",
      "name": "Alice",
      "email": "[email protected]"
    },
    "paymentLink": null,
    "settlement": {
      "grossAmount": "50.00",
      "fees": {
        "systemFee": "0.60",
        "gasFee": "0.08",
        "totalFee": "0.68"
      },
      "netAmount": "49.32"
    }
  }
}

Signing string: event|txHash|fromAddress|toAddress|amount|blockNumber|timestamp|userId

address_created

address_created

Fired when a new deposit address is generated for your account — whether via the API, dashboard, or a payment link flow. Useful for syncing your internal records.

{
  "event": "address_created",
  "timestamp": "2024-01-15T10:00:00.000Z",
  "data": {
    "addressId": 42,
    "address": "0xDepositAddress...",
    "network": "BNB Smart Chain",
    "mode": "production",
    "chainId": 56,
    "explorerUrl": "https://bscscan.com/address/0xDepositAddress...",
    "customerUid": "cust_001",
    "note": null,
    "metadata": null,
    "userId": 7
  }
}

withdrawal_completed

withdrawal_completed

Fired when a withdrawal request has been fully processed and the USDT transfer is confirmed on-chain. The payload includes all transaction hashes and the final fee breakdown.

{
  "event": "withdrawal_completed",
  "timestamp": "2024-01-15T11:00:00.000Z",
  "signature": "c9f2a4...",
  "data": {
    "withdrawalId": 55,
    "mode": "production",
    "network": "BNB Smart Chain",
    "toAddress": "0xDestinationAddress...",
    "requestedAmount": "100.00",
    "actualAmount": "99.20",
    "feeAmount": "0.80",
    "feeMode": "deduct_from_amount",
    "transferTxHash": "0xwithdraw123...",
    "feeTxHash": "0xfee456...",
    "gasSwapTxHash": null,
    "attempts": 1,
    "usdtBalanceBefore": "120.00",
    "bnbBalanceBefore": "0.005",
    "enqueuedAt": "2024-01-15T10:59:50.000Z",
    "processedAt": "2024-01-15T11:00:05.000Z",
    "userId": 7
  }
}

Signing string: Generic format — event|timestamp|{sorted JSON of data}

withdrawal_failed

withdrawal_failed

Fired when a withdrawal request could not be completed after all retry attempts. The failureReason field describes what went wrong. The withdrawal remains in a failed state and can be retried manually from the dashboard.

{
  "event": "withdrawal_failed",
  "timestamp": "2024-01-15T11:05:00.000Z",
  "signature": "d1e3b7...",
  "data": {
    "withdrawalId": 55,
    "mode": "production",
    "network": "BNB Smart Chain",
    "toAddress": "0xDestinationAddress...",
    "requestedAmount": "100.00",
    "feeMode": "deduct_from_amount",
    "attempts": 3,
    "failureReason": "Insufficient USDT balance",
    "onChainBalance": "85.00",
    "shortfall": "15.00",
    "enqueuedAt": "2024-01-15T10:59:50.000Z",
    "userId": 7
  }
}

Signing string: Generic format — event|timestamp|{sorted JSON of data}

Signature Verification

Every webhook includes a signature field — an HMAC-SHA256 hex digest computed over a deterministic signing string using your webhook secret. Always verify this before processing the event.

Signing string formats
EventsFormat
deposit_detected, deposit_settledevent|txHash|fromAddress|toAddress|amount|blockNumber|timestamp|userId
all other eventsevent|timestamp|{sorted JSON of data}
Node.js verification
const crypto = require('crypto');

function verifyWebhook(payload, receivedSignature, secret) {
  const { event, data, timestamp } = payload;

  let signingString;
  if (event === 'deposit_detected' || event === 'deposit_settled') {
    signingString = [
      event,
      data.txHash,
      data.fromAddress,
      data.toAddress,
      data.amount,
      String(data.blockNumber),
      timestamp,
      data.userId != null ? String(data.userId) : '',
    ].join('|');
  } else {
    const sorted = Object.keys(data).sort().reduce((acc, k) => {
      acc[k] = data[k];
      return acc;
    }, {});
    signingString = `${event}|${timestamp}|${JSON.stringify(sorted)}`;
  }

  const expected = crypto
    .createHmac('sha256', secret)
    .update(signingString)
    .digest('hex');

  if (expected.length !== receivedSignature.length) return false;
  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(receivedSignature, 'hex'),
  );
}
Python verification
import hmac, hashlib, json, secrets

def verify_webhook(payload: dict, received_signature: str, secret: str) -> bool:
    event = payload["event"]
    data = payload["data"]
    timestamp = payload["timestamp"]

    if event in ("deposit_detected", "deposit_settled"):
        signing_string = "|".join([
            event,
            data["txHash"],
            data["fromAddress"],
            data["toAddress"],
            data["amount"],
            str(data["blockNumber"]),
            timestamp,
            str(data["userId"]) if data.get("userId") is not None else "",
        ])
    else:
        sorted_data = json.dumps(dict(sorted(data.items())), separators=(",", ":"))
        signing_string = f"{event}|{timestamp}|{sorted_data}"

    expected = hmac.new(
        secret.encode(), signing_string.encode(), hashlib.sha256
    ).hexdigest()

    return secrets.compare_digest(expected, received_signature)