Docs
Everything you need to integrate the crypt.pe gateway. Stripe-style API surface, HMAC-signed webhooks, non-custodial — funds settle directly to your wallet.
Quickstart
- Sign up at crypt.pe/signup and add your wallet address.
- Upgrade to Business ($29/mo) to unlock API keys and the gateway. The personal payment link stays free — only programmatic orders require a paid plan.
- Open the dashboard → Gateway card → click new key. Save the sk_live_* and whsec_* values.
- From your backend, create an order:
curl -X POST https://crypt.pe/api/v1/payments \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"amount_usd": 49,
"accepted_coins": ["eth","matic","usdc-erc20"],
"return_url": "https://your-site.com/thanks",
"webhook_url": "https://your-site.com/webhooks/cryptpe"
}'The response includes checkout_url. Redirect your customer there. When payment confirms on-chain, we POST a signed event to your webhook_url.
Authentication
All gateway endpoints use bearer authentication with your sk_live_* key. Never expose the secret key in client-side code. The companion pk_live_* publishable key is reserved for future browser SDKs; today it is not strictly required.
Authorization: Bearer sk_live_<your-secret-key>
Keys are revocable from the dashboard. Revoked keys fail all subsequent requests with HTTP 401.
Payments
Create a new order. Returns the order plus a checkout_url you should redirect the customer to.
// optional headers
- Idempotency-Key — replays with the same key return the original order.
// request body
{
"amount_usd": 49,
"accepted_coins": ["eth","matic","usdc-erc20"], // optional, default: all enabled
"return_url": "https://your-site.com/thanks", // optional
"webhook_url": "https://your-site.com/webhooks/cryptpe",
"customer_email": "buyer@example.com", // optional
"metadata": { "wc_order_id": 1234 }, // optional
"expires_in_minutes": 30 // optional, default 30, max 1440
}// response
{
"order_id": "cp_QxAy6YABSYiIDIDEquW2ir",
"status": "pending",
"amount_usd": 49,
"accepted_coins": ["eth","matic","usdc-erc20"],
"expires_at": "2026-05-30T20:30:00+00:00",
"checkout_url": "https://crypt.pe/checkout/cp_QxAy6YABSYiIDIDEquW2ir",
"merchant": { "username": "...", "display_name": "...", "accent_color": "#10D982" }
}Fetch an order you previously created. Use this to poll status if your webhook handler is offline; webhooks are still the recommended channel.
Hosted checkout
The checkout_url we return is a public page that runs end-to-end. You don't normally need to call these endpoints; they exist so you could build a custom checkout UI if you ever wanted one.
Returns the order without authentication — used by the checkout page. Stale pending orders past expires_at are auto-flipped to expired on read.
Customer picks which coin to pay in. Locks chosen_coin, chosen_address, and expected_amount_crypto on the order so the webhook matcher has stable values.
{ "coin": "matic" }Webhooks
When an order changes status we POST a JSON event to your webhook_url. Verify every webhook — assume anyone can fire a fake one at your endpoint.
Event types
- payment.confirmed — the on-chain transfer matched the expected amount within ±2% tolerance. Mark the customer's order as paid.
- payment.underpaid — less than the expected amount arrived. Mark as on-hold for manual review.
- payment.overpaid — more than expected. Refund the difference manually or absorb it.
- payment.expired — the order expired before any payment was received.
Sample body
{
"id": "evt_1706713200_a1b2c3d4",
"type": "payment.confirmed",
"created": "2026-05-30T14:00:00+00:00",
"data": {
"order_id": "cp_QxAy6YABSYiIDIDEquW2ir",
"status": "confirmed",
"amount_usd": 49,
"coin": "matic",
"amount_crypto": 136.111,
"tx_hash": "0xabc...",
"network": "MATIC_MAINNET",
"customer_email": "buyer@example.com",
"metadata": { "wc_order_id": 1234 }
}
}Signature verification
Every request carries an X-Cryptpe-Signature header in the format t=<unix-ts>,v1=<hex>. Re-compute HMAC-SHA256 of "<t>.<raw-body>" with your whsec_* and compare constant-time.
// Node.js using our SDK
const Cryptpe = require('./cryptpe');
const cryptpe = new Cryptpe(process.env.CRYPTPE_SECRET_KEY);
app.post('/webhooks/cryptpe', express.raw({type:'application/json'}), (req, res) => {
try {
const event = cryptpe.webhooks.verify(
req.body,
req.headers['x-cryptpe-signature'],
process.env.CRYPTPE_WEBHOOK_SECRET,
);
// event.type === 'payment.confirmed' → mark order paid
res.json({ received: true });
} catch (e) {
res.status(400).send('invalid signature');
}
});// PHP — matches what our WooCommerce plugin uses
$body = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_CRYPTPE_SIGNATURE'];
preg_match('/t=(\d+),v1=([0-9a-f]+)/', $sig, $m);
[$_, $t, $v1] = $m;
$expected = hash_hmac('sha256', $t.'.'.$body, getenv('CRYPTPE_WEBHOOK_SECRET'));
if (!hash_equals($expected, $v1)) { http_response_code(400); exit('bad sig'); }
$event = json_decode($body, true);Delivery & retries
We attempt delivery up to 3 times with exponential backoff (1s, 5s, 25s). Respond with any 2xx status to acknowledge. Non-2xx, timeouts (>10s), and connection errors trigger retries. Every attempt is logged on our side and visible in the dashboard.
Errors
We return standard HTTP status codes and a JSON body with a single detail string.
- 400 — your request was malformed (missing fields, invalid coins, amount ≤ 0).
- 401 — missing or invalid Authorization header.
- 404 — order not found (or you don't own it).
- 503 — price feed temporarily unavailable. Retry in a few seconds.
Testing
Use the test webhook button in your dashboard's Gateway card to fire a sample payment.confirmed event at your handler — without spending any crypto. The signature is computed with your real whsec_* so you can validate end-to-end.
SDKs & plugins
Drop-in integrations for popular stacks. The raw HTTP API works from anything that can speak JSON.
Operator: HD wallets (self-hosted)
Self-hosting crypt.pe? Configure HD-derived receiving addresses so every order gets its own on-chain destination — auto-detect becomes an unambiguous address → order_id lookup instead of a fuzzy amount-and-time match. Two trust modes are supported; pick whichever matches your operations posture.
Mode A — xpub-only (recommended)
The host receives only an extended public key per rail. It can derive every incoming-funds address forever, but cannot move funds. The matching private key stays on a hardware wallet or air-gapped machine — that's the machine you use when it's time to sweep.
Derive each xpub at the change-chain level (one above the address index) and paste into the env var listed below.
# Each xpub covers all non-hardened children — i.e. /0, /1, /2, … # Derive AT the change-chain level (NOT at the address-index level). CRYPTPE_EVM_XPUB="xpub6D…" # path: m/44'/60'/0'/0 (Ethereum / Polygon / Base / Arbitrum) CRYPTPE_BTC_XPUB="zpub6t…" # path: m/84'/0'/0'/0 (Bitcoin native segwit, bc1q…) CRYPTPE_TRON_XPUB="xpub6…" # path: m/44'/195'/0'/0 (Tron, base58 T…)
- Sparrow Wallet (BTC): Settings → Keystores → Master Public Key. Choose script type P2WPKH (Native SegWit) → copy the zpub….
- Electrum (BTC): Wallet → Information → Master Public Key. Create the wallet as Native SegWit. Copy the zpub….
- Ledger / Trezor (BTC): use Sparrow or Electrum in watch-only mode against the hardware wallet → export the zpub from there. Keeps the seed on-device.
- EVM (Ethereum + L2s): MetaMask doesn't export xpubs directly. Use iancoleman.io/bip39 offline (download the HTML, run on an air-gapped machine), select BIP44 · ETH, set internal/external = 0, and copy Account Extended Public Key.
- Tron: same flow as EVM via iancoleman.io — choose coin TRX in the BIP44 panel, copy the account-level xpub….
Mode B — mnemonic (dev / single-host)
Drop the full BIP39 mnemonic into one env var. Simple, but anyone who reads it can sweep your funds — only do this on hosts you fully trust (private dev, single-merchant deployments behind a hardware firewall).
CRYPTPE_HD_MNEMONIC="word1 word2 … word12"
xpub takes priority per rail: if both CRYPTPE_EVM_XPUB and CRYPTPE_HD_MNEMONIC are set, the EVM rail uses the xpub. Useful for mixed setups — e.g. xpub for BTC (hardware wallet) but mnemonic for EVM during initial rollout.
Sweep checklist
- Open /admin/cohorts → the hd addresses table shows the next derivation index + preview address per rail.
- On your air-gapped machine, derive the same index from the master seed and confirm the address matches. If it doesn't, stop — the host's xpub is wrong.
- Sweep the funds. Addresses below next_index are the ones that have been handed out; anything still holding a balance is fair game.