PYLEX
Get Started
Developer Documentation

Build with PYLEX

Everything you need to integrate crypto payment processing into your application β€” REST API, webhooks, and non-custodial smart contracts.

Overview

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.

How it Works

01

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.

02

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.

03

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.

04

Payment detected on-chain

Blockchain monitors watch for incoming transactions. Once detected the invoice moves to CONFIRMING status and starts counting confirmations.

05

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.

06

Auto-sweep

The sweep worker calls withdrawAllERC20 / withdrawAllEth on the factory, moving funds to your configured withdrawAddress automatically.

Invoice status lifecycle

Every invoice transitions through a defined set of states:

CREATED
PENDING
CONFIRMING
PAID
EXPIRED
CANCELED

API Reference

Authentication

All endpoints (except GET /health) require the header:

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

INVOICES_CREATEINVOICES_READWALLETS_READSETTINGS_READWEBHOOKS_READ

Endpoints

GET/healthLiveness check β€” no auth required
POST/invoicesCreate a new invoice
GET/invoices/:idGet full invoice details
GET/invoices/:id/statusLightweight status poll
POST/invoices/:id/cancelCancel an invoice
GET/tokens/payableList accepted tokens
POST/wallets/generatePre-generate deposit wallet addresses

POST /invoices

Creates a new invoice. Returns the invoice object and a hosted payment URL.

Request

json
{
  "amount": 49.99,
  "fiatCurrency": "USD",
  "expiresIn": 1800,
  "externalId": "order_abc123",
  "callbackUrl": "https://your-app.com/webhooks/pylex",
  "metadata": {
    "userId": "usr_42",
    "plan": "pro"
  }
}
FieldTypeRequiredDescription
amountnumberrequiredInvoice amount in fiat currency
fiatCurrencystringrequiredISO 4217 code β€” USD, EUR, …
expiresInnumberoptionalTTL in seconds (defaults to merchant setting)
externalIdstringoptionalYour internal order/payment ID
callbackUrlstringoptionalWebhook URL for status events
metadataobjectoptionalArbitrary JSON stored with the invoice

Response β€” 201 Created

json
{
  "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"
  }
}

GET /invoices/:id

Returns the full invoice object including deposit address, chain info, and exchange rate.

json
{
  "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 format

json
{
  "error": "NOT_FOUND",
  "message": "Invoice not found",
  "details": []
}
HTTPMeaning
200 / 201Success
400Invalid request body
401Missing or invalid API key
403Permission not granted to this key
404Resource not found
500Internal server error

Webhook Integration

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.

Delivery details

Max attempts

4

Retry backoff

1m β†’ 5m β†’ 30m β†’ 2h

Timeout

10 s per request

Request headers

http
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

Payload structure

json
{
  "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.

Signature verification

Every request includes an HMAC-SHA256 signature of the raw JSON body using your webhook secret. Always verify this before processing the event.

typescript
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

  • Always verify the HMAC signature before processing.
  • Respond with 200 quickly β€” heavy processing should be queued.
  • Use externalId or metadata to map events to your orders.
  • Make your handler idempotent β€” retries can send the same event multiple times.
  • Test your endpoint with the "Send test webhook" button in the dashboard.

Smart Contracts

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

FeeLessFactory
one per chain β€” deploys per-user factories
PylExWalletFactory
per-merchant clone β€” manages deposit wallets
DepositWallet Γ—N
per-invoice clone β€” accepts ETH + ERC20

DepositWallet.sol

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.

solidity
// 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);

PylExWalletFactory.sol

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.

solidity
// ── 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 wallet

Address derivation (off-chain preview)

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

typescript
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

  • Only the PylExWalletFactory owner (merchant) can withdraw funds β€” PYLEX has no withdraw access.
  • The autoSweepWallet (hot EOA) is restricted to create + sweep β€” it cannot change the withdraw address.
  • All withdrawals use nonReentrant guards against reentrancy attacks.
  • EIP-1167 minimal proxies cost ~50% less gas than full deployments.

Ready to integrate?

Generate your API key in the dashboard and start accepting crypto payments in minutes.