Everything you need to integrate crypto payment processing into your application β REST API, webhooks, and non-custodial smart contracts.
PYLEX is a non-custodial cryptocurrency payment gateway. Merchants create fiat-denominated invoices; customers pay in crypto on any supported EVM chain or TRON. Each payment lands in a deterministic deposit wallet derived from a smart contract factory β funds never touch PYLEX infrastructure.
Non-custodial
Funds go directly to your smart-contract factory. PYLEX never holds assets.
Real-time detection
Blockchain monitors detect incoming transactions within seconds of broadcast.
Multi-chain
EVM-compatible chains (BSC, Polygon, β¦) and TRON supported out of the box.
Webhook driven
Every status change fires a signed webhook β no polling needed on your side.
REST API
Simple REST gateway secured with API keys and scoped permissions.
Smart contracts
Deterministic per-invoice wallets via Clones (EIP-1167) for gas efficiency.
Create an invoice
Your server calls POST /invoices with the fiat amount and currency. PYLEX returns an invoice ID and a public payment URL to redirect your customer to.
Customer selects payment method
On the hosted payment page the customer picks a cryptocurrency and a target chain. PYLEX resolves the real-time exchange rate and shows the exact crypto amount.
Deterministic wallet assigned
A deposit address is computed from the WalletFactory smart contract using CREATE2 β the same salt always produces the same address across chains. No gas is spent until funds arrive.
Payment detected on-chain
Blockchain monitors watch for incoming transactions. Once detected the invoice moves to CONFIRMING status and starts counting confirmations.
Confirmation & webhook
After the required number of confirmations the invoice status becomes PAID. A signed webhook is delivered to your callbackUrl with full payment details.
Auto-sweep
The sweep worker calls withdrawAllERC20 / withdrawAllEth on the factory, moving funds to your configured withdrawAddress automatically.
Every invoice transitions through a defined set of states:
All endpoints (except GET /health) require the header:
x-api-key: pg_live_<your_api_key>API keys are generated in the dashboard. They are shown only once, stored as SHA-256 hashes, and support scoped permissions:
/healthLiveness check β no auth required/invoicesCreate a new invoice/invoices/:idGet full invoice details/invoices/:id/statusLightweight status poll/invoices/:id/cancelCancel an invoice/tokens/payableList accepted tokens/wallets/generatePre-generate deposit wallet addressesCreates a new invoice. Returns the invoice object and a hosted payment URL.
{
"amount": 49.99,
"fiatCurrency": "USD",
"expiresIn": 1800,
"externalId": "order_abc123",
"callbackUrl": "https://your-app.com/webhooks/pylex",
"metadata": {
"userId": "usr_42",
"plan": "pro"
}
}| Field | Type | Required | Description |
|---|---|---|---|
| amount | number | required | Invoice amount in fiat currency |
| fiatCurrency | string | required | ISO 4217 code β USD, EUR, β¦ |
| expiresIn | number | optional | TTL in seconds (defaults to merchant setting) |
| externalId | string | optional | Your internal order/payment ID |
| callbackUrl | string | optional | Webhook URL for status events |
| metadata | object | optional | Arbitrary JSON stored with the invoice |
{
"id": "01924d7a-f1b2-7abc-9def-123456789abc",
"externalId": "order_abc123",
"amount": "49.99",
"status": "CREATED",
"paymentMethod": null,
"createdAt": "2025-06-01T12:00:00.000Z",
"expiresAt": "2025-06-01T12:30:00.000Z",
"invoiceToken": {
"id": "tok_01",
"symbol": "USD"
}
}Returns the full invoice object including deposit address, chain info, and exchange rate.
{
"id": "01924d7a-f1b2-7abc-9def-123456789abc",
"externalId": "order_abc123",
"amount": "49.99",
"status": "CONFIRMING",
"paymentMethod": "CRYPTO",
"depositAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"cryptoAmount": "150000000",
"paidAt": null,
"metadata": { "userId": "usr_42", "plan": "pro" },
"chain": { "id": 56, "name": "BNB Smart Chain", "slug": "bsc" },
"token": { "symbol": "USDT", "decimals": 6 },
"createdAt": "2025-06-01T12:00:00.000Z",
"expiresAt": "2025-06-01T12:30:00.000Z"
}{
"error": "NOT_FOUND",
"message": "Invoice not found",
"details": []
}| HTTP | Meaning |
|---|---|
| 200 / 201 | Success |
| 400 | Invalid request body |
| 401 | Missing or invalid API key |
| 403 | Permission not granted to this key |
| 404 | Resource not found |
| 500 | Internal server error |
PYLEX pushes signed HTTP POST requests to your callbackUrl whenever an invoice status changes. Configure a default URL and secret in the dashboard, or override per-invoice.
Max attempts
4
Retry backoff
1m β 5m β 30m β 2h
Timeout
10 s per request
POST https://your-app.com/webhooks/pylex HTTP/1.1
Content-Type: application/json
X-Webhook-Signature: sha256=<hmac_hex>
X-Webhook-Id: 01924e00-aaaa-7bbb-8ccc-ddddeeeeeeee
X-Webhook-Timestamp: 1748779200{
"event": "invoice.paid",
"invoiceId": "01924d7a-f1b2-7abc-9def-123456789abc",
"invoice": {
"id": "01924d7a-f1b2-7abc-9def-123456789abc",
"externalId": "order_abc123",
"amount": "49.99",
"invoiceToken": "USD",
"status": "PAID",
"paymentMethod": "CRYPTO",
"depositAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"cryptoAmount": "150000000",
"paidAt": "2025-06-01T12:08:34.000Z",
"metadata": { "userId": "usr_42", "plan": "pro" }
},
"payment": {
"txHash": "0xabc123...def456",
"fromAddress": "0xCustomerWalletAddress",
"amount": "150000000",
"confirmations": 12,
"status": "CONFIRMED"
},
"timestamp": "2025-06-01T12:08:35.000Z"
}The payment field is present only for crypto payments. For TELEGRAM_STARS invoices it is omitted.
Every request includes an HMAC-SHA256 signature of the raw JSON body using your webhook secret. Always verify this before processing the event.
import crypto from "crypto";
import type { Request, Response } from "express";
const WEBHOOK_SECRET = process.env.PYLEX_WEBHOOK_SECRET!;
export function pylexWebhook(req: Request, res: Response) {
const signature = req.headers["x-webhook-signature"] as string;
const payload = JSON.stringify(req.body);
const expected =
"sha256=" +
crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(payload)
.digest("hex");
if (
signature.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
) {
return res.status(401).send("Invalid signature");
}
const { event, invoice } = req.body;
if (event === "invoice.paid") {
// Fulfill the order associated with invoice.externalId
await fulfillOrder(invoice.externalId, invoice.metadata);
}
res.sendStatus(200);
}Best practices
PYLEX uses two Solidity contracts per EVM chain to provide non-custodial deposit wallets with minimal gas overhead. Wallet addresses are deterministic β computed before deployment using CREATE2 (EIP-1167 Clones).
Contract architecture
Minimal upgradeable clone (ReentrancyGuardUpgradeable) that accepts ETH and ERC20 tokens. Only the owning PylExWalletFactory can withdraw funds. Fee-on-transfer tokens are supported β the actual received delta is tracked.
// Accept native ETH
receive() external payable
// Accept ERC20 (caller must approve first)
function depositToken(address token, uint256 amount) external nonReentrant
// Withdraw ETH β only WalletFactory owner
function withdrawEth(address to, uint256 amount) external onlyOwner nonReentrant
// Withdraw ERC20 β only WalletFactory owner
function withdrawToken(
address token,
address to,
uint256 amount
) external onlyOwner nonReentrant
// Events
event EthDeposited(address indexed from, uint256 amount);
event TokenDeposited(address indexed from, address indexed token, uint256 amount);Per-merchant factory that creates deterministic DepositWallet clones via Clones.cloneDeterministic. A bytes32 salt uniquely identifies each wallet. The same salt on different chains produces the same address, enabling cross-chain address reuse.
// ββ Wallet management ββββββββββββββββββββββββββββββββββββββ
// Deploy a new DepositWallet clone for the given salt
function createWallet(bytes32 salt)
external onlyOwnerOrSweeper returns (address)
// Compute address without deploying (pre-show to customer)
function computeWalletAddress(bytes32 salt)
external view returns (address)
// Look up a deployed wallet by its salt
function getWalletBySalt(bytes32 salt)
external view returns (address)
// ββ Withdrawals ββββββββββββββββββββββββββββββββββββββββββββ
// Withdraw from a single wallet (by salt)
function withdrawEthBySalt(bytes32 salt, uint256 amount) external onlyOwner
function withdrawERC20BySalt(
bytes32 salt,
address token,
uint256 amount
) external onlyOwner
// Sweep all wallets in one tx (used by auto-sweep worker)
function withdrawAllEth(bytes32[] calldata salts) external onlyOwnerOrSweeper
function withdrawAllERC20(
address token,
bytes32[] calldata salts
) external onlyOwnerOrSweeper
// ββ Access control βββββββββββββββββββββββββββββββββββββββββ
// onlyOwner β full control (withdrawals, config)
// onlyOwnerOrSweeper β subset (create, sweep) β safe for hot walletBecause the address is deterministic, PYLEX can show the deposit address to the customer before the wallet is deployed. You can replicate this with ethers.js:
import { ethers } from "ethers";
const FACTORY_ADDRESS = "0xYourWalletFactoryAddress";
const WALLET_IMPL = "0xDepositWalletImplementation";
/**
* Returns the deterministic address for a given salt without deploying.
* Mirrors Clones.cloneDeterministic logic (EIP-1167 + CREATE2).
*/
function predictDepositAddress(salt: string): string {
// EIP-1167 minimal proxy bytecode wrapping the implementation
const creationCode =
"0x3d602d80600a3d3981f3363d3d373d3d3d363d73" +
WALLET_IMPL.slice(2).toLowerCase() +
"5af43d82803e903d91602b57fd5bf3";
return ethers.getCreate2Address(
FACTORY_ADDRESS,
ethers.zeroPadValue(ethers.toBeHex(salt), 32),
ethers.keccak256(creationCode),
);
}
// Example
const address = predictDepositAddress("0x0000...0001");
console.log("Deposit address:", address);Security notes
Generate your API key in the dashboard and start accepting crypto payments in minutes.