One API and one checkout for every way people pay in Bangladesh
Paynex is a self-hosted payment gateway. Create a payment with one signed API call, send the customer to the hosted checkout, and get a webhook when the money is confirmed — bank transfer, Bangla QR, crypto and more.
# 1. create a payment (from your server) POST https://paynex.xyz/v1/payments { "amount": "1500.00", "merchant_ref": "INV-1042", "return_url": "https://shop.example/thanks", "cancel_url": "https://shop.example/cart" } # 2. send the customer to "checkout_url": "https://paynex.xyz/c/FLeipf0FGgJ2agDku55a7Jh…" # 3. your webhook receives X-PG-Event: payment.paid { "event": "payment.paid", "data": { "id": "92189b87-…", "status": "paid" } }
How it works
Three steps. Your server holds the API key; the customer only ever sees the hosted checkout.
Create a payment
Call POST /v1/payments with the amount, your invoice number and return URLs. The response carries a checkout_url. Same Idempotency-Key = same payment, so retries are safe.
Customer pays on the hosted checkout
A fast, mobile-friendly page with your logo where the customer picks a method and pays. Bank transfer and Bangla QR take a receipt upload; crypto shows a unique amount and address and confirms itself on-chain.
Get the webhook, ship the order
Paynex posts a signed payment.paid event to your URL and retries for 24 hours. Verify the signature, fetch GET /v1/payments/{id}, deliver. Refunds are one call away.
Payment methods
Enable methods per store from the admin. Customers see only what you turn on.
Also built in: Sandbox Pay simulator for test keys, payment links from the admin (QR, WhatsApp/email share), refunds, per-store accounts statement, review queue, audit log and a "Risk & device" panel (IP, device, language, referer) on every payment.
Quick start
Every request is signed with your key (pk_…) and secret (sk_…) — see Authentication. Or skip the recipe and use an SDK.
KEY_ID=pk_sandbox_…; SECRET=sk_sandbox_…; BASE=https://paynex.xyz
BODY='{"amount":"1500.00","merchant_ref":"INV-1042","return_url":"https://shop.example/thanks","cancel_url":"https://shop.example/cart"}'
TS=$(date +%s); NONCE=$(openssl rand -hex 16)
SIG=$(printf '%s\n%s\nPOST\n/v1/payments\n%s' "$TS" "$NONCE" "$(printf '%s' "$BODY" | sha256sum | cut -d' ' -f1)" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $NF}')
curl -s $BASE/v1/payments -H "Authorization: Paynex-HMAC key_id=$KEY_ID" \
-H "X-PG-Timestamp: $TS" -H "X-PG-Nonce: $NONCE" -H "X-PG-Signature: $SIG" \
-H "Idempotency-Key: INV-1042-1500.00" -H "Content-Type: application/json" -d "$BODY"const { Paynex } = require('./paynex'); // /docs/sdk/paynex-node.zip
const px = new Paynex({ baseUrl: 'https://paynex.xyz', keyId: process.env.PAYNEX_KEY_ID, secret: process.env.PAYNEX_SECRET });
const p = await px.createPayment({ amount: '1500.00', merchant_ref: 'INV-1042',
return_url: 'https://shop.example/thanks', cancel_url: 'https://shop.example/cart' }, 'INV-1042-1500.00');
res.redirect(p.checkout_url);
// webhook (Express): raw body is required for the signature
app.post('/paynex/webhook', express.raw({ type: () => true }), Paynex.webhookHandler(WEBHOOK_SECRET, async (ev) => {
if (ev.event !== 'payment.paid') return;
const full = await px.getPayment(ev.data.id);
if (full.status === 'paid') await markOrderPaid(full.merchant_ref, full.id);
}));require 'Paynex.php'; // /docs/sdk/paynex-php.zip
$px = new Paynex('https://paynex.xyz', getenv('PAYNEX_KEY_ID'), getenv('PAYNEX_SECRET'));
$p = $px->createPayment([
'amount' => '1500.00', 'merchant_ref' => 'INV-1042',
'return_url' => 'https://shop.example/invoice/1042', 'cancel_url' => 'https://shop.example/invoice/1042',
], 'INV-1042-1500.00');
header('Location: ' . $p['checkout_url']);
// webhook.php
$ev = Paynex::verifyWebhook(getenv('PAYNEX_WEBHOOK_SECRET'), $_SERVER['HTTP_X_PG_SIGNATURE'] ?? null, file_get_contents('php://input'));
if ($ev['event'] === 'payment.paid' && $px->getPayment($ev['data']['id'])['status'] === 'paid') markInvoicePaid($ev['data']['id']);c := paynex.New("https://paynex.xyz", os.Getenv("PAYNEX_KEY_ID"), os.Getenv("PAYNEX_SECRET")) // /docs/sdk/paynex-go.zip
p, err := c.CreatePayment(ctx, paynex.CreatePaymentParams{
Amount: "1500.00", MerchantRef: "INV-1042",
ReturnURL: "https://shop.example/thanks", CancelURL: "https://shop.example/cart",
}, "INV-1042-1500.00")
http.Redirect(w, r, p.CheckoutURL, http.StatusSeeOther)
http.Handle("/paynex/webhook", paynex.WebhookHandler(webhookSecret, func(ctx context.Context, ev *paynex.Event) error {
if ev.Event != "payment.paid" { return nil }
d, _ := ev.Payment()
full, err := c.GetPayment(ctx, d.ID)
if err == nil && full.Paid() { markOrderPaid(full.MerchantRef, full.ID) }
return err
}))final px = Paynex(baseUrl: 'https://paynex.xyz', keyId: env['PAYNEX_KEY_ID']!, secret: env['PAYNEX_SECRET']!); // /docs/sdk/paynex-dart.zip
final p = await px.createPayment(
amount: '1500.00', merchantRef: 'INV-1042',
returnUrl: 'myapp://paynex/return', cancelUrl: 'myapp://paynex/cancel', // Flutter deep links
idempotencyKey: 'INV-1042-1500.00');
// hand p.checkoutUrl to the app → launchUrl(..., mode: LaunchMode.inAppBrowserView)
final ev = Paynex.verifyWebhook(webhookSecret, headers['x-pg-signature'], rawBody);
if (ev.event == 'payment.paid' && (await px.getPayment(ev.data['id'])).paid) await markOrderPaid(ev.data['id']);Built for merchants and developers
Signed API, no shared sessions
Every call carries an HMAC-SHA256 signature over method, path, body and a nonce. Keys have scopes; secrets are encrypted at rest.
Webhooks you can trust
Signed deliveries, unique delivery ids, retries at 1m · 5m · 30m · 2h · 12h · 24h, manual redelivery from the admin.
Mobile apps
Open the checkout in an in-app browser, get the customer back via myapp:// deep links or App Links, poll a public status JSON. SDKs for the backend.
Payment links
No code needed: generate a link from the admin, share by QR, WhatsApp or email, watch it get paid.
Multi-store
Stores with their own domains, logos, methods, webhook secrets, API keys, account statements and users (owner, store admin, ops, viewer).
Crypto done properly
Unique amounts on static addresses, treasury sweeps to save gas, withdrawals with owner approval — or Binance direct deposit with zero custody.
Risk & device data
IP, device, language and referer for every checkout step, with heuristic flags on the payment page — ready for rules.
Sandbox mode
Test keys and a payment simulator run the full flow — webhooks, verify, refunds — without moving money.
SDKs & downloads
Dependency-free, single-file clients for your backend. Same surface everywhere: create / get / list / cancel / verify payments, refunds, store info, webhook and return-redirect verification.
| Language | Download | Notes |
|---|---|---|
| Go | paynex-go.zip · README | paynex.New(base, keyID, secret), WebhookHandler for net/http |
| Node.js 18+ | paynex-node.zip · README | CommonJS + TypeScript types, Express webhookHandler |
| Dart 3 / Flutter | paynex-dart.zip · README + Flutter guide | backend client; the app side needs no key (url_launcher + app_links) |
| PHP 7.4+ | paynex-php.zip · README | single Paynex.php, cURL — WHMCS, Laravel, WordPress |
Documentation
Everything the integration needs, served by the gateway itself so it always matches the running version.
Usage at a glance
Where things live once you are set up.