Your website owns products and orders. PABEL PAY owns the payment. You send an opaque order_id, an amount and a currency; you receive payment status by signed webhook.
Base URL: https://pabelpay.pabelprimezone.shop/api/v1
Every request is signed with your Secret Key. Send these headers:
X-Public-Key | Your public key (pk_…) |
X-Timestamp | Unix seconds. Must be within ±5 minutes. |
X-Nonce | Unique per request, 8–64 chars [A-Za-z0-9_-]. A reused nonce is rejected (replay protection). |
X-Signature | hex HMAC-SHA256 of the canonical string below, keyed with the Secret Key |
Idempotency-Key | Optional. Safe retries of payment/create. |
canonical = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + sha256_hex(RAW_BODY)
signature = hex( HMAC_SHA256(canonical, SECRET_KEY) )
PATH is the request path as sent (for example /api/v1/payment/create), without host or query string. For requests without a body, RAW_BODY is the empty string.
POST /payment/create
{
"order_id": "ORDER-20260928-10001", // required, opaque, unique per website
"amount": 500.00, // required, from YOUR database
"currency": "BDT", // BDT, USD, EUR, GBP, INR
"customer_name": "Rahim", "customer_email": "rahim@example.com", "customer_phone": "01700000000",
"success_url": "https://yourshop.com/pay/return", "fail_url": "...", "cancel_url": "...",
"webhook_url": "https://yourshop.com/pay/webhook"
}
Response 201:
{ "success": true, "payment_id": "PAY-…", "order_id": "ORDER-…", "amount": 500.0, "currency": "BDT",
"checkout_url": "https://pabelpay.pabelprimezone.shop/checkout/PAY-%E2%80%A6", "status": "pending" }
Redirect the customer to checkout_url. Calling create again with the same order_id returns the existing pending payment; a different amount for the same order_id returns 409. Return URLs must be on your registered website's domain. Product fields (product_name, items, cart, sku, quantity, …) are rejected on purpose.
GET /payment/{payment_id} | Payment with status timeline |
GET /payment/status/{payment_id} | Current status |
POST /payment/verify {"payment_id":"…"} | Re-checks with the gateway (gateway payments). verified:true only when PAID. |
POST /payment/cancel {"payment_id":"…"} | Cancels a pending payment |
GET /merchant/profile | Merchant identity for your keys |
GET /website | Website settings for your keys |
GET /ping | Connectivity + signature check |
Events: payment.created, payment.pending, payment.processing, payment.approved, payment.completed, payment.rejected, payment.failed, payment.cancelled. Payload contains payment data only:
{
"event": "payment.completed",
"event_id": "EVT-9F3A6B21C04D77E1",
"payment_id": "PAY-1A2B3C4D5E6F7A8B",
"order_id": "ORDER-20260928-10001",
"merchant_id": "MER-XXXXXXXXXXXX",
"website_id": "WEB-XXXXXXXXXXXX",
"amount": 500.0,
"currency": "BDT",
"transaction_id": "TXN-XXXX",
"status": "paid",
"timestamp": "2026-09-28T10:15:00+00:00",
"signature": "<64 hex chars, always the LAST member>"
}
Headers: X-PabelPay-Event, X-PabelPay-Event-Id, X-PabelPay-Timestamp, X-PabelPay-Signature. Respond with any 2xx within 15 seconds. Otherwise delivery is retried with backoff (1m, 5m, 15m, 1h, 3h, 12h, 24h) up to 8 attempts; you can also retry manually from the dashboard. The same event may arrive more than once — deduplicate by event_id.
signature = hex( HMAC_SHA256( X-PabelPay-Timestamp + "." + UNSIGNED_BODY, WEBHOOK_SECRET ) )
UNSIGNED_BODY is the raw request body with the trailing ,"signature":"<hex>" member removed. Do not re-encode the JSON; strip that suffix from the raw string. Compare with a constant-time function and reject timestamps older than 5 minutes. Our PHP client does all of this in handleWebhook().
// Node.js
const m = raw.match(/^(.*),"signature":"([a-f0-9]{64})"\}$/s);
const unsigned = m[1] + '}';
const expected = crypto.createHmac('sha256', WEBHOOK_SECRET).update(ts + '.' + unsigned).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-pabelpay-signature']));
Payment status (owned by PABEL PAY): pending, processing, approved, paid, failed, rejected, cancelled, expired. Order status (pending, confirmed, processing, completed, rejected, delivered…) belongs to your website and is never sent to or changed by us.
Errors: {"success":false,"error":{"code":"…","message":"…"}}. Common codes: invalid_signature, timestamp_out_of_range, replay_detected, rate_limited (429), duplicate_order (409), product_data_not_allowed (422). Default limit: 120 requests/minute per website.
verify) proves payment.amount and currency match your order.