Documentationv1.0
● REST APIPSO Licensedv1.0 Stable

Paperless API Reference

A unified payment gateway for Bangladesh — accept cards, bKash, Nagad, Rocket, and internet banking through a single, well-documented API built for developers.

📅 Updated June 2025|📖 15 min read|👤 For developers

All API requests must be made over HTTPS. The API accepts and returns JSON in all request and response bodies.

Base URLsProduction: https://api.paperlessltd.com/v1
Sandbox: https://sandbox-api.paperlessltd.com/v1

Quick Start

Get your first payment running in under 10 minutes. You’ll need a Paperless merchant account and your API keys from the dashboard.

1

Create a merchant account

Sign up at portal.paperlessltd.com. Complete KYC — approval usually within 24 hours.

2

Get your API keys

Find your merchant_id and api_key under Dashboard → Settings → API Keys.

3

Initiate a payment

Call POST /payments/initiate and redirect your customer to the returned payment_url.

4

Handle the webhook

Configure a webhook endpoint to receive real-time payment confirmations.

cURL
curl -X POST https://api.paperlessltd.com/v1/payments/initiate   -H "Content-Type: application/json"   -H "Authorization: Bearer YOUR_API_KEY"   -d '{
    "merchant_id": "PLMRCH_001",
    "amount": 5000,
    "currency": "BDT",
    "order_id": "ORD_20250101_001",
    "customer_name": "Rahim Uddin",
    "customer_email": "rahim@example.com",
    "redirect_url": "https://yoursite.com/success",
    "webhook_url": "https://yoursite.com/webhooks/paperless"
  }'

Authentication

Paperless uses Bearer token authentication. Pass your API key in every request via the Authorization header.

HTTP Header
Authorization: Bearer pl_live_xxxxxxxxxxxxxxxxxxxxxxxx

Key types

TypePrefixDescription
Livepl_live_Real transactions on production. Never expose in client-side code.
Sandboxpl_test_Test mode only. Safe for staging environments.
Keep keys secretNever commit API keys to version control or expose them in the browser. Rotate compromised keys immediately from Dashboard → Settings → API Keys.

Environments

Paperless maintains two fully isolated environments with separate credentials and data.

EnvironmentAPI BaseDashboard
Productionapi.paperlessltd.com/v1portal.paperlessltd.com
Sandboxsandbox-api.paperlessltd.com/v1sandbox-dashboard.paperlessltd.com
Full sandbox parityAll payment methods work in sandbox with simulated responses. See Testing for test credentials.

SDKs & Libraries

Official libraries handle request signing, retries, and response parsing. All are open-source on GitHub.


Payments

The core of Paperless. Initiate a payment session and redirect your customer to the hosted checkout. The page handles provider selection, OTP, and confirmation entirely.

POST/payments/initiateCreate a payment session

Returns a payment_url valid for 30 minutes. Redirect the customer there immediately after initiation.

Request body

FieldTypeDescription
merchant_idstringRequiredYour merchant identifier from the dashboard.
amountintegerRequiredAmount in BDT paisa (1 BDT = 100 paisa). Min: 100.
currencystringRequiredMust be BDT.
order_idstringRequiredYour unique order reference. Used for idempotency.
customer_namestringRequiredFull name of the customer.
customer_emailstringRequiredCustomer email address.
customer_phonestringOptionalE.164 format e.g. +8801700000000.
redirect_urlstringRequiredRedirect here on success.
cancel_urlstringOptionalRedirect here if customer cancels.
webhook_urlstringOptionalPer-transaction webhook override.
metadataobjectOptionalArbitrary key-value pairs echoed in webhook payloads.

Response

200Success
400Bad Request
401Unauthorized
{
  "success": true,
  "txn_id": "PL_TXN_9F3A2C8D",
  "order_id": "ORD_20250101_001",
  "status": "INITIATED",
  "payment_url": "https://pay.paperlessltd.com/checkout/PL_TXN_9F3A2C8D",
  "expires_at": "2025-01-01T11:30:00+06:00",
  "amount": 5000,
  "currency": "BDT"
}
{
  "success": false,
  "error": {
    "code": "INVALID_AMOUNT",
    "message": "Amount must be a positive integer in paisa.",
    "field": "amount"
  }
}
{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or missing API key."
  }
}

Payment Status

Fetch the current state of any transaction by its txn_id. Useful for polling if you haven’t set up webhooks yet, or for verifying a webhook payload.

GET/payments/{txn_id}Retrieve a single payment
cURL
curl https://api.paperlessltd.com/v1/payments/PL_TXN_9F3A2C8D   -H "Authorization: Bearer pl_live_xxxx"
{
  "txn_id": "PL_TXN_9F3A2C8D",
  "order_id": "ORD_20250101_001",
  "status": "COMPLETED",
  "amount": 5000,
  "currency": "BDT",
  "payment_method": "bkash",
  "provider_ref": "BKH8736210000",
  "completed_at": "2025-01-01T11:03:22+06:00"
}

Possible statuses

INITIATEDPROCESSINGCOMPLETEDFAILEDCANCELLEDREFUNDEDPARTIAL_REFUNDEXPIRED

Refunds

Issue full or partial refunds on any COMPLETED payment. Refunds are processed asynchronously — listen for the refund.completed webhook event to confirm.

POST/refundsInitiate a refund
FieldTypeDescription
txn_idstringRequiredTransaction ID of the original completed payment.
amountintegerOptionalAmount in paisa. Omit to refund the full amount.
reasonstringOptionalReason for refund. Stored for audit.
idempotency_keystringOptionalUnique key to prevent duplicate submissions.
cURL
curl -X POST https://api.paperlessltd.com/v1/refunds   -H "Authorization: Bearer pl_live_xxxx"   -H "Idempotency-Key: refund-ORD_001"   -d '{"txn_id":"PL_TXN_9F3A2C8D","amount":2500}'

Transactions

Retrieve a paginated list of all transactions for your merchant account. Useful for reconciliation and reporting.

GET/transactionsList transactions
Query paramTypeDescription
pageintegerPage number. Default: 1.
limitintegerResults per page. Default: 20, max: 100.
statusstringFilter by status e.g. COMPLETED.
fromstringISO 8601 start date.
tostringISO 8601 end date.

Merchants

Merchant management endpoints require an admin API key (pl_admin_). Contact support@paperlessltd.com to request admin access.

Admin access requiredStandard merchant keys return 403 Forbidden on these endpoints.

Webhooks

Webhooks deliver real-time event notifications to your server. Configure your endpoint in Dashboard → Settings → Webhooks.

Retry policyFailed deliveries are retried 5 times with exponential backoff: 1 min → 5 min → 30 min → 2 hrs → 12 hrs. Dead events are logged in your dashboard.

Payload structure

{
  "event": "payment.completed",
  "event_id": "evt_A1B2C3D4E5",
  "timestamp": "2025-01-01T11:03:22+06:00",
  "data": {
    "txn_id": "PL_TXN_9F3A2C8D",
    "order_id": "ORD_20250101_001",
    "status": "COMPLETED",
    "amount": 5000,
    "payment_method": "bkash"
  }
}

Webhook Event Reference

EventDescription
payment.initiatedA new payment session has been created.
payment.completedPayment confirmed. Funds settle in the next batch.
payment.failedPayment failed at the provider level.
payment.cancelledCustomer closed or cancelled the checkout.
payment.expiredPayment session timed out (30 minutes).
refund.initiatedRefund submitted to the provider.
refund.completedRefund returned to the customer successfully.
refund.failedRefund failed. Manual review may be required.
settlement.processedSettlement batch sent to your bank account.

Signature Verification

Every webhook includes a X-Paperless-Signature header — an HMAC-SHA256 of the raw body using your webhook secret. Always verify before processing.

Node.js
const crypto = require('crypto');

function verifyWebhook(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected), Buffer.from(signature)
  );
}

app.post('/webhooks/paperless', (req, res) => {
  const sig = req.headers['x-paperless-signature'];
  if (!verifyWebhook(req.rawBody, sig, process.env.WH_SECRET))
    return res.status(401).send('Invalid signature');
  // handle event...
  res.sendStatus(200);
});
PHP
function verifyWebhook($payload, $sig, $secret): bool {
  $expected = hash_hmac('sha256', $payload, $secret);
  return hash_equals($expected, $sig);
}
$payload = file_get_contents('php://input');
$sig     = $_SERVER['HTTP_X_PAPERLESS_SIGNATURE'];
if (!verifyWebhook($payload, $sig, $_ENV['WH_SECRET'])) {
  http_response_code(401); exit;
}

Idempotency

Supply an Idempotency-Key header on any POST request to safely retry without the risk of duplicate operations.

HTTP Header
Idempotency-Key: order-ORD_20250101_001-attempt-1
Best practiceUse your order_id as the key. Keys expire after 24 hours. Duplicate requests within that window return the original cached response with a 200.

Error Codes

All errors return a consistent JSON body. code is machine-readable; message is human-readable.

{
  "success": false,
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "The customer wallet does not have sufficient balance.",
    "field": null
  }
}
HTTPMeaning
200Success.
400Bad request — missing or invalid parameters.
401Unauthorized — invalid or missing API key.
403Forbidden — insufficient permissions.
404Resource not found.
409Conflict — duplicate order_id.
422Unprocessable entity — validation failed.
429Too many requests. See rate limits.
500Server error. Contact support if persistent.

Testing

Use the sandbox environment with these credentials. No real money moves and all transactions reset daily.

Test cards

Card numberExpiryCVVOutcome
4242 4242 4242 4242Any futureAny Success
4000 0000 0000 0002Any futureAny Declined
4000 0000 0000 9995Any futureAny Insufficient funds

Test mobile wallets

ProviderPhoneOTPOutcome
bKash01700000001123456 Success
Nagad01700000002123456 Success
Rocket01700000003123456 Success
bKash01700000099Any Failed

Rate Limits

Limits are per API key and reset every 60 seconds. When exceeded a 429 is returned with a Retry-After header.

EndpointRequests / min
POST /payments/initiate120
GET /payments/:txn_id300
POST /refunds60
GET /transactions60
All others120
Rate limit headersEvery response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset.