Naafi Pay

Integration Guide

Everything needed to take payments on your own site, in one page. Pick your platform below, copy the code, paste in your API key, and you are live. The same three things are used by every language: your API key, the endpoint /api/public/v1/payments, and thecheckout_url you redirect the buyer to. No SDK or library is required anywhere.

What you need before you start

  1. A NaafiPay account with at least one store (brand).
  2. An API key — create it in the dashboard under Integration.
  3. A webhook URL on your site, plus its signing secret (also on the Integration page).
  4. A server that can make outbound HTTPS requests. Shared cPanel hosting is fine.
TipOne place for everything
Log in and open Integration in the sidebar. Your API base URL, merchant (brand) ID, store slug, API key, webhook URL, signing secret and gateway merchant/store IDs are all there with show-and-copy buttons, next to the same code samples you see below.

The flow in five steps

  1. Create an API key. Generate a key below. Copy it once and store it on your server (environment variable), never in browser code.
  2. Send a test payment request. Pick your language, paste the snippet, run it. The response contains a checkout_url — open it and you will see your branded checkout.
  3. Redirect the buyer to checkout_url. Your customer picks bKash, Nagad, Rocket, card or bank there. NaafiPay handles verification automatically.
  4. Add a webhook so your site knows when money arrives. Register your endpoint URL, copy the signing secret, and verify every payload with HMAC-SHA256 before trusting it.
  5. Go live. Complete verification, switch your key from test to live, and start collecting real payments.

Authentication

Every request carries your key in the Authorization header. Live keys start with pg_live_ and test keys with pg_test_. Keys are SHA-256 hashed at rest, scoped to a single store, and can be restricted to specific server IPs.

Request header
1Authorization: Bearer pg_live_xxxxxxxxxxxxxxxx
2Content-Type: application/json
WarningNever put a live key in browser code
Anything inside HTML, JavaScript, or a mobile app can be read by anyone. Always create the payment from your server and send only the checkout_url to the browser.

Ready-made plugins (no code)

Endpoints you will use

  • GET /api/public/v1/me — verify your API key and see which store it belongs to.
  • POST /api/public/v1/payments — create a payment, returns checkout_url.
  • GET /api/public/v1/payments/{id} — fetch one payment by id or reference.
  • POST /api/public/v1/payments/{id}/refund — request a refund (full or partial).
  • POST /api/public/v1/verify-payment — confirm a payment server-side.
  • GET /api/public/v1/payments — list payments with paging.
  • POST /api/public/v1/payment-links — create a reusable payment link.
  • POST /api/public/v1/products, PATCH /api/public/v1/products/{id} — catalog sync.
  • POST /api/public/v1/subscriptions, POST /api/public/v1/subscriptions/{id}/cancel — recurring billing.
  • GET /api/public/v1/openapi — OpenAPI spec for Postman and code generators.

Pick your platform

cURL / Terminal

The fastest way to confirm your key works. Run this in any terminal — if you get a checkout_url back, your integration is live.

1. Create a payment

cURL / Terminal — 1. Create a payment
1curl -X POST https://naafipay.com/api/public/v1/payments \
2 -H "Authorization: Bearer pg_live_xxxxxxxxxxxxxxxx" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "amount": 1500,
6 "currency": "BDT",
7 "customer_name": "Tasin Fahim",
8 "customer_email": "tasin@example.com",
9 "customer_phone": "01700000000",
10 "reference_id": "ORD-1001",
11 "metadata": { "order_id": "1001" }
12 }'

2. Verify a payment

cURL / Terminal — 2. Verify a payment
1curl -X POST https://naafipay.com/api/public/v1/verify-payment \
2 -H "Authorization: Bearer pg_live_xxxxxxxxxxxxxxxx" \
3 -H "Content-Type: application/json" \
4 -d '{ "invoice_id": "udp_01HZX..." }'

3. List payments

cURL / Terminal — 3. List payments
1curl "https://naafipay.com/api/public/v1/payments?limit=20" \
2 -H "Authorization: Bearer pg_live_xxxxxxxxxxxxxxxx"

Webhooks

A webhook is a small POST request NaafiPay sends to your site the moment something happens, so you never have to poll. Add your endpoint URL on the Integration page, copy the signing secret, and verify every request before trusting it.

Headers we send
1x-paygate-signature: sha256=<hex HMAC-SHA256 of the raw body>
2x-paygate-signature-v2: t=<unix-seconds>,v1=<hex HMAC-SHA256 of "timestamp.body">
3content-type: application/json
Example payload
1{
2 "event": "payment.completed",
3 "created_at": "2026-01-14T09:20:11.000Z",
4 "data": {
5 "id": "pay_01HZX...",
6 "amount": 1500,
7 "currency": "BDT",
8 "status": "completed",
9 "reference_id": "ORD-1001",
10 "payment_method": "bkash",
11 "transaction_id": "9F3K21AB",
12 "customer_name": "Tasin Fahim",
13 "customer_email": "tasin@example.com"
14 }
15}

Events you can subscribe to:

  • payment.completed — Money received and verified — fulfil the order.
  • payment.failed — The attempt failed. Ask the customer to retry.
  • payment.cancelled — The customer abandoned the checkout.
  • payment.refunded — A refund was issued for this payment.
  • invoice.paid — An invoice you sent has been paid.
  • payment_link.created — A new payment link was created in your account.
TipAlways answer 200 quickly
Do the slow work (emails, invoices) after responding. Anything other than a 2xx response is retried with exponential backoff.

Going live checklist

  1. Identity verification complete in the dashboard.
  2. At least one payment gateway active under Gateways.
  3. Live API key stored as a server environment variable, test key removed.
  4. Webhook endpoint reachable over HTTPS and verifying the signature.
  5. One real end-to-end payment made and refunded.

Troubleshooting

  • 401 unauthorized — the header must be Authorization: Bearer pg_…, and the key must not be revoked or IP-restricted away from your server.
  • No checkout_url — the store has no active gateway, or the amount is below the gateway minimum.
  • Webhook never arrives — the URL must be public HTTPS; check the delivery log under Webhooks in the dashboard.
  • Signature mismatch — hash the raw request body, not a re-serialised JSON object, and keep the sha256= prefix in the comparison.
Updated at: May 30, 2026