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.
Funds go directly to your smart-contract factory. PYLEX never holds assets.
Blockchain monitors detect incoming transactions within seconds of broadcast.
EVM-compatible chains (BSC, Polygon, …) and TRON supported out of the box.
Every status change fires a signed webhook — no polling needed on your side.
Simple REST gateway secured with API keys and scoped permissions.
Deterministic per-invoice wallets via Clones (EIP-1167) for gas efficiency.
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.
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.
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.
Blockchain monitors watch for incoming transactions. Once detected the invoice moves to CONFIRMING status and starts counting confirmations.
After the required number of confirmations the invoice status becomes PAID. A signed webhook is delivered to your callbackUrl with full payment details.
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.
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);
}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).
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);Generate your API key in the dashboard and start accepting crypto payments in minutes.
Open Dashboard