Developer Docs
Accept payments with CryptoEze
One API for JazzCash, Easypaisa, QR Payments and USDT (BEP-20). Follow the steps in order. You only need the steps for the methods on your merchant account.
Before you start
- Sign in and open Merchant → API keys.
- Click Issue secret and copy the Secret key. It is shown once. Keep it on your server only. Never put it in a browser, app, or public code.
- Add your Webhook URL on the same page and copy the Webhook signing secret.
https://cryptoeze.com/api/v1Authorization: Bearer YOUR_SECRET_KEYIdempotency-Key: a-unique-id-per-orderContent-Type: application/jsonIf your server retries the same request with the same key, you get the same payment back, never a second one. Use your order ID.
Redirect checkout
Create the payment, then send your customer to checkout_url. We show the payment page, and bring the customer back to your return_url. Every field in the response sits inside data.
- No payment screen to build
- Works for JazzCash, Easypaisa, QR and USDT
Only trust the webhook (or a server-side status check) to mark an order paid. Never trust the customer returning to your page.
Step 1
JazzCash
Amount is in PKR. The customer approves the payment in the JazzCash app.
1. Create the payment
curl -X POST https://cryptoeze.com/api/v1/pkr \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Idempotency-Key: order-1001" \
-H "Content-Type: application/json" \
-d '{
"method": "jazzcash",
"amount": "1500.00",
"order_id": "order-1001",
"customer_email": "buyer@example.com",
"return_url": "https://your-site.com/thank-you"
}'
Inside data you get the payment id, status: "pending" and a checkout_url. Send the customer to checkout_url. They enter their JazzCash number there and approve it on their phone.
Step 2
Easypaisa
Exactly the same as JazzCash. Only the method changes.
curl -X POST https://cryptoeze.com/api/v1/pkr \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Idempotency-Key: order-1002" \
-H "Content-Type: application/json" \
-d '{
"method": "easypaisa",
"amount": "2500.00",
"order_id": "order-1002",
"customer_email": "buyer@example.com",
"return_url": "https://your-site.com/thank-you"
}'
Send the customer to checkout_url. They approve it in the Easypaisa app.
Step 3
QR Payments
The customer scans a QR with any Pakistani bank app, JazzCash or Easypaisa. No mobile number is needed.
curl -X POST https://cryptoeze.com/api/v1/pkr \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Idempotency-Key: order-1003" \
-H "Content-Type: application/json" \
-d '{
"method": "qr",
"amount": "999.00",
"order_id": "order-1003",
"customer_email": "buyer@example.com",
"return_url": "https://your-site.com/thank-you"
}'
Send the customer to checkout_url. We show the QR and the countdown. customer_email is required on every PKR create.
Step 4
USDT BEP-20
Amount is in USDT. The customer sends USDT on BNB Smart Chain (BEP-20) to a unique address for this invoice.
curl -X POST https://cryptoeze.com/api/v1/invoices \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Idempotency-Key: order-1004" \
-H "Content-Type: application/json" \
-d '{
"amount": "25.00",
"order_id": "order-1004",
"description": "Pro plan",
"customer_email": "buyer@example.com",
"return_url": "https://your-site.com/thank-you"
}'
Inside data you get id, checkout_url, pay_address, send (exact USDT to send) and expires_at. Send the customer to checkout_url. We show the address, QR, amount and live confirmations. CryptoEze users can also pay from their balance with 0 fee.
Payments are final after the required confirmations, 15 by default (about a minute). If the customer sends less, the invoice becomes partial. More becomes overpaid. Only official USDT on BEP-20 is ever credited. Other tokens are ignored.
Webhooks
We POST signed JSON to your Webhook URL whenever a payment changes. Answer with HTTP 200; we retry otherwise.
payment.paid · payment.failed · payment.refundedinvoice.paid · invoice.partial · invoice.expired · invoice.cancelled · invoice.invalidX-CryptoEze-Signature: t=TIMESTAMP,v1=SIGNATUREVerify the signature (PHP)
$raw = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_CRYPTOEZE_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $header), $p); // t=…, v1=…
$expected = hash_hmac('sha256', $p['t'] . '.' . $raw, 'YOUR_WEBHOOK_SECRET');
if (! hash_equals($expected, $p['v1'] ?? '') || abs(time() - (int) $p['t']) > 300) {
http_response_code(400); exit; // not from CryptoEze
}
$event = json_decode($raw, true);
if ($event['type'] === 'payment.paid' || $event['type'] === 'invoice.paid') {
// mark $event['data']['order_id'] as paid, once
}
http_response_code(200);
Verify the signature (Node.js)
const crypto = require('crypto');
// use the RAW request body, not parsed JSON
const parts = Object.fromEntries(header.split(',').map(s => {
const i = s.indexOf('=');
return [s.slice(0, i), s.slice(i + 1)];
}));
const expected = crypto.createHmac('sha256', WEBHOOK_SECRET).update(parts.t + '.' + rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 || '');
const ok = a.length === b.length && crypto.timingSafeEqual(a, b)
&& Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
Every event has a unique id. Save it; if the same id arrives again, just answer 200.
Status & errors
GET /api/v1/pkr/IDGET /api/v1/invoices/IDGET /api/v1/balance| HTTP | Code | What to do |
|---|---|---|
| 400 | invalid_request | Fix the field named in message. |
| 404 | invalid_request | That payment or invoice is not on your account. |
| 401 | authentication | Check the Bearer secret key. |
| 403 | forbidden | This method is not active on your account yet. |
| 409 | idempotency_conflict | Same Idempotency-Key with a different body. Use a new key. |
| 429 | rate_limited | Slow down and retry after a few seconds. |
Go live checklist
- Your merchant account is approved for the methods you use.
- Your website domain is verified (return URLs must be on it).
- Secret key is stored on the server only.
- Webhook URL is set, and the signature check passes.
- You tested one small payment per method end to end.
Need help? Open Merchant → Support.