Need a hand?

Setting up the panel or the Android app? The merchant guide walks through it.

Merchant Guide

Welcome To Nagorikpay Docs Last updated: 2026-09-04

Nagorikpay is a simple and Secure payment automation tool which is designed to use personal account as a payment gateway so that you can accept payments from your customer through your website where you will find a complete overview on how Nagorikpay works and how you can integrate Nagorikpay API in your website

New: For app and bot integrations without opening a payment page, see the Direct Number API documentation. It covers payment-number requests, TrxID submission, sandbox guidance and signed webhooks.

API Introduction

Nagorikpay Payment Gateway enables Merchants to receive money from their customers by temporarily redirecting them to www.Nagorikpay.com. The gateway is connecting multiple payment terminal including card system, mobile financial system, local and International wallet. After the payment is complete, the customer is returned to the merchant's site and seconds later the Merchant receives notification about the payment along with the details of the transaction. This document is intended to be utilized by technical personnel supporting the online Merchant's website. Working knowledge of HTML forms or cURL is required. You will probably require test accounts for which you need to open accounts via contact with Nagorikpay.com or already provided to you.

API Operation

REST APIs are supported in two environments. Use the Sandbox environment for testing purposes, then move to the live environment for production processing. When testing, generate an order url with your test credentials to make calls to the Sandbox URIs. When you're set to go live, use the live credentials assigned to your new signature key to generate a live order url to be used with the live URIs. Your server has to support cURL system. For HTML Form submit please review after cURL part we provide HTML Post method URL also

Live API End Point (For Create Payment URL):

POSThttps://secure-pay.nagorikpay.com/api/payment/create

Payment Verify API:

POSThttps://secure-pay.nagorikpay.com/api/payment/verify

Parameter Details

Variables Need to POST to Initialize Payment Process in gateway URL

Field Name Description Required Example Values
cus_name Customer Full Name Yes John Doe
cus_email Email address of the customer Yes john@gmail.com
amount The total amount payable. Please note that you should skip the the trailing zeros in case the amount is a natural number. Yes 10 or 10.50 or 10.6
success_url URL to which the customer will be returned when the payment is made successfully. The customer will be returned to the last page on the Merchant's website where he should be notify the payment successful. Yes https://yourdomain.com/sucess.php
cancel_url URL to return customer to your product page or home page. Yes https://yourdomain.com/cancel.php
webhook_url IPN callback URL No https://yourdomain.com/webhook.php
metadata Optional extra data (JSON) to attach to the payment — for example your internal order or invoice reference. No {"order_id":"AB-123"}

Variables Needs For Payment Verify

Field Name Description Required Example Values
transaction_id Transaction id received as a query parameter from the success URL provided during payment creation. Yes OVKPXW165414

Headers Details

Header Name Value
Content-Type application/json
API-KEY App key From Brands

Securing your integration (Webhook Signature)

Everything you send us is authenticated with the API-KEY header above. Everything we send back to your webhook_url is authenticated with an HMAC-SHA256 signature — that is what proves a webhook really came from Nagorikpay, and that nothing was altered on the way to your server.

Building a new integration? Verify the signature. Every delivery already carries X-NagorikPay-Timestamp and X-NagorikPay-Signature. The full recipe — the exact signed string, PHP and Node.js examples, and replay protection — is in Webhook (IPN) → Signature (HMAC-SHA256). Add the check before you go live; retrofitting it later is far more work than writing it now.
Already live and not verifying? You do not have to change anything. Those headers have always been sent, so nothing on your side breaks. A delivery is signed with your brand's Webhook Secret if you have generated one, and with your brand API key otherwise — which is the original behaviour. Generate a secret from merchant panel → Brands → Webhook Secret and update your verifier in the same step only when you want the stronger scheme (a leaked API key can then no longer forge webhooks).

Sandbox (Test Mode)

Build and test your integration against the sandbox before you touch real money. The sandbox is a separate API host that behaves like the live one — same endpoints, same request and response shapes — but no money moves, no SMS is matched, and nothing it creates ever appears in your live transactions, reports or reconciliation.

EnvironmentBase URLCredential
Livehttps://api.nagorikpay.com/Your brand API key
Sandboxhttps://sandbox-api.nagorikpay.com/Your Sandbox API Key (sk_test_…)

Getting your sandbox key

Open merchant panel → Brands → edit a brand → Sandbox API Key (Test Mode) and press Generate. The key is shown once, starts with sk_test_, and is stored separately from your live key.

  • Send it in exactly the same API-KEY header — no other change to your code.
  • It is accepted only on sandbox-api.nagorikpay.com. The live API rejects it, and the sandbox also rejects an unknown key — so the two can never be confused.
  • Your live key keeps working on the sandbox too, so you can point an existing integration at the sandbox host without re-issuing anything.
  • Rotate invalidates the previous key immediately. Remove disables sandbox access for that brand.

What the sandbox does and does not do

BehaviourSandbox
Same create-request / verify endpoints and response shapeYes
Webhooks fired to your webhook_url, signed the same wayYes
Rows marked is_test = 1, hidden from live lists and reportsYes
Real money collectedNo
Appears in live transactions, reconciliation or payoutsNo
Never put a live key in test code, or a sandbox key in production. The sk_test_ prefix exists so that a key found in a log or a screenshot is recognisable at a glance.

Integration

You can integrate our payment gateway into your PHP Laravel WordPress WooCommerce sites.

Sample Request


      <?php

      $curl = curl_init();

      curl_setopt_array($curl, array(
        CURLOPT_URL => 'https://secure-pay.nagorikpay.com/api/payment/create',
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => '',
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 0,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => 'POST',
        CURLOPT_POSTFIELDS =>'{"success_url":"yourdomain.com/success","cancel_url":"yourdomain.com/cancel","webhook_url":"yourdomain.com/webhook","metadata":{"phone":"016****"},"amount":"10"}',
        CURLOPT_HTTPHEADER => array(
          'API-KEY: YOUR_BRAND_API_KEY',
          'Content-Type: application/json'
        ),
      ));

      $response = curl_exec($curl);

      curl_close($curl);
      echo $response;

      ?>
      

      <?php
      $client = new Client();
      $headers = [
        'API-KEY' => 'YOUR_BRAND_API_KEY',
        'Content-Type' => 'application/json'
      ];
      $body = '{
        "success_url": "yourdomain.com/success",
        "cancel_url": "yourdomain.com/cancel",
        "webhook_url": "yourdomain.com/webhook",
        "metadata": {
          "phone": "016****"
        },
        "amount": "10"
      }';
      $request = new Request('POST', 'https://secure-pay.nagorikpay.com/api/payment/create', $headers, $body);
      $res = $client->sendAsync($request)->wait();
      echo $res->getBody();
      ?>
      

      const axios = require('axios');
      let data = JSON.stringify({
        "success_url": "yourdomain.com/success",
        "cancel_url": "yourdomain.com/cancel",
        "webhook_url": "yourdomain.com/webhook",
        "metadata": {
          "phone": "016****"
        },
        "amount": "10"
      });

      let config = {
        method: 'post',
        maxBodyLength: Infinity,
        url: 'https://secure-pay.nagorikpay.com/api/payment/create',
        headers: { 
          'API-KEY': 'YOUR_BRAND_API_KEY', 
          'Content-Type': 'application/json'
        },
        data : data
      };

      axios.request(config)
      .then((response) => {
        console.log(JSON.stringify(response.data));
      })
      .catch((error) => {
        console.log(error);
      });


      

      import requests
      import json

      url = "https://secure-pay.nagorikpay.com/api/payment/create"

      payload = json.dumps({
        "success_url": "yourdomain.com/success",
        "cancel_url": "yourdomain.com/cancel",
        "webhook_url": "yourdomain.com/webhook",
        "metadata": {
          "phone": "016****"
        },
        "amount": "10"
      })
      headers = {
        'API-KEY': 'YOUR_BRAND_API_KEY',
        'Content-Type': 'application/json'
      }

      response = requests.request("POST", url, headers=headers, data=payload)

      print(response.text)


        

      package main

      import (
        "fmt"
        "strings"
        "net/http"
        "io/ioutil"
      )

      func main() {

        url := "https://secure-pay.nagorikpay.com/api/payment/create"
        method := "POST"

        payload := strings.NewReader(`{"success_url":"yourdomain.com/success","cancel_url":"yourdomain.com/cancel","webhook_url":"yourdomain.com/webhook","metadata":{"phone":"01521412457"},"amount":"10"}`)

        client := &http.Client {
        }
        req, err := http.NewRequest(method, url, payload)

        if err != nil {
          fmt.Println(err)
          return
        }
        req.Header.Add("API-KEY", "YOUR_BRAND_API_KEY")
        req.Header.Add("Content-Type", "application/json")

        res, err := client.Do(req)
        if err != nil {
          fmt.Println(err)
          return
        }
        defer res.Body.Close()

        body, err := ioutil.ReadAll(res.Body)
        if err != nil {
          fmt.Println(err)
          return
        }
        fmt.Println(string(body))
      }
        

Response Details

Field Name Type Description
Success Response
status bool TRUE
message String Message for Status
payment_url String Payment Link (where customers will complete their payment)
Error Response
status bool FALSE
message String Message associated with the error response
Completing Payment Page task you will be redirected to success or cancel page based on transaction status with the following Query Parameters: yourdomain.com/(success/cancel)?transactionId=******&paymentMethod=***&paymentAmount=**.**&paymentFee=**.**&status=pending or success or failed

Verify Request


      <?php


      $curl = curl_init();

      curl_setopt_array($curl, array(
        CURLOPT_URL => 'https://secure-pay.nagorikpay.com/api/payment/verify',
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => '',
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 0,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => 'POST',
        CURLOPT_POSTFIELDS =>'{"transaction_id":"ABCDEFH"}',
        CURLOPT_HTTPHEADER => array(
          'API-KEY: YOUR_BRAND_API_KEY',
          'Content-Type: application/json'
        ),
      ));

      $response = curl_exec($curl);

      curl_close($curl);
      echo $response;


      ?>
      

      <?php
      $client = new Client();
      $headers = [
        'API-KEY' => 'YOUR_BRAND_API_KEY',
        'Content-Type' => 'application/json'
      ];
      $body = '{
        "transaction_id": "ABCDEFH"
      }';
      $request = new Request('POST', 'https://secure-pay.nagorikpay.com/api/payment/verify', $headers, $body);
      $res = $client->sendAsync($request)->wait();
      echo $res->getBody();

      ?>
      

      const axios = require('axios');
      let data = JSON.stringify({
        "transaction_id": "ABCDEFH"
      });

      let config = {
        method: 'post',
        maxBodyLength: Infinity,
        url: 'https://secure-pay.nagorikpay.com/api/payment/verify',
        headers: { 
          'API-KEY': 'YOUR_BRAND_API_KEY', 
          'Content-Type': 'application/json'
        },
        data : data
      };

      axios.request(config)
      .then((response) => {
        console.log(JSON.stringify(response.data));
      })
      .catch((error) => {
        console.log(error);
      });



      

      import http.client
      import json

      conn = http.client.HTTPSConnection("secure-pay.nagorikpay.com")
      payload = json.dumps({
        "transaction_id": "ABCDEFH"
      })
      headers = {
        'API-KEY': 'YOUR_BRAND_API_KEY',
        'Content-Type': 'application/json'
      }
      conn.request("POST", "/api/payment/verify", payload, headers)
      res = conn.getresponse()
      data = res.read()
      print(data.decode("utf-8"))

        

      package main

      import (
        "fmt"
        "strings"
        "net/http"
        "io/ioutil"
      )

      func main() {

        url := "https://secure-pay.nagorikpay.com/api/payment/verify"
        method := "POST"

        payload := strings.NewReader(`{"transaction_id":"ABCDEFH"}`)

        client := &http.Client {
        }
        req, err := http.NewRequest(method, url, payload)

        if err != nil {
          fmt.Println(err)
          return
        }
        req.Header.Add("API-KEY", "YOUR_BRAND_API_KEY")
        req.Header.Add("Content-Type", "application/json")

        res, err := client.Do(req)
        if err != nil {
          fmt.Println(err)
          return
        }
        defer res.Body.Close()

        body, err := ioutil.ReadAll(res.Body)
        if err != nil {
          fmt.Println(err)
          return
        }
        fmt.Println(string(body))
      }
        
Sample Response

      {
          "cus_name": "John Doe",
          "cus_email": "john@gmail.com",
          "amount": "900.000",
          "transaction_id": "OVKPXW165414",
          "metadata": {
            "phone": "015****"
          },
          "payment_method": "bkash",
          "status": "COMPLETED"
      }
      

Response Details

Field Name Type Description
Success Response
status string COMPLETED or PENDING or ERROR
cus_name String Customer Name
cus_email String Customer Email
amount String Amount
transaction_id String Transaction id Generated by System
metadata json Metadata used for Payment creation
Error Response
status bool FALSE
message String Message associated with the error response

Webhook (IPN) Notifications

When you create a payment you may pass a webhook_url. Nagorikpay then POSTs the outcome of that payment to your URL automatically — you never have to poll for it. Every delivery is recorded in your merchant panel → Webhook Logs, where you can inspect the exact request your server received (or missed) and resend it at any time.

How & when the webhook fires

  • Immediately after checkout — the customer finishes paying on the gateway page. If the payment method confirmed the money instantly you will receive status = completed. Methods that need manual review (bank transfer / QR) send status = pending first and the money is confirmed later.
  • When a pending payment auto-verifies — once the money is confirmed inside the review window, Nagorikpay sends a second webhook with status = completed.
  • A cancelled or abandoned payment never triggers a webhook — the customer is simply returned to your cancel_url.

Payload fields

Nagorikpay sends a form-urlencoded POST to your webhook_url. The same fields are also appended to the redirect back to your success_url, so the two always agree.

Field Description
paymentMethod The payment method used (e.g. bkash, nagad, card).
transactionId Unique Nagorikpay transaction id for this payment.
method_transaction_id The payment-provider reference the customer entered (e.g. the bKash/Nagad TrxID), when the method captured one. Empty for methods without one.
paymentAmount The amount the customer paid.
paymentFee Fee deducted for this payment.
status Outcome: completed | pending | failed.

Handle & verify

Reply to the webhook as fast as possible with any HTTP 2xx — Nagorikpay treats a 2xx response as delivered. Do the heavy work afterwards. Before fulfilling an order it is safest to confirm the payment with our verify endpoint (see Verify Request above). A minimal PHP receiver looks like:


<?php
// 1) Acknowledge fast — Nagorikpay treats any 2xx as delivered.
http_response_code(200);

// 2) Capture what was POSTed.
$paymentMethod         = $_POST['paymentMethod'] ?? '';
$transactionId         = $_POST['transactionId'] ?? '';
$methodTransactionId   = $_POST['method_transaction_id'] ?? '';
$paymentAmount         = $_POST['paymentAmount'] ?? '';
$status                = $_POST['status'] ?? '';

// 3) Confirm via api/payment/verify before fulfilling (see "Verify Request").
//    Then mark the order as paid in your own database.
                  

Delivery logs & resend (merchant panel)

  • Open Webhook Logs from your merchant sidebar to see every delivery for your account: target URL, timestamp, the exact payload that was POSTed and the HTTP response code/body your server returned.
  • Click Resend to replay a delivery — Nagorikpay re-sends the exact stored payload to the same URL (you do not provide the URL again) and logs the new attempt as a fresh entry so you can confirm it arrived.
  • The default view shows only the webhooks that point to your own site. Confirmations Nagorikpay sends back to itself to close its Invoice / Add Funds flows appear under Platform Callbacks and normally need no action from you.
  • Failed deliveries are retried automatically on a backoff schedule — 1 minute, 5 minutes, 30 minutes, 2 hours, then 6 hours. Each attempt is logged as its own row, so you can see exactly how many tries a delivery took before it succeeded (or gave up).

Signature verification (HMAC-SHA256)

Your webhook_url is reachable by anyone who learns it, so the URL alone cannot prove a request really came from Nagorikpay. To close that gap, Nagorikpay can sign every webhook with a secret that only you and the gateway know, so you can verify both who sent it and that nothing was altered in transit.

Always on, nothing to enable: every webhook has always carried a signature, and it still does — so if you already verify it, nothing changes. The only question is which key signs it: until you generate a Webhook Secret for a brand the gateway signs with your brand API key (the original behaviour); once you generate one it switches to that dedicated secret automatically. Generate it from merchant panel → Brands → edit brand → Webhook Secret → Generate.

Headers sent

Header Meaning
X-NagorikPay-Timestamp Unix time (seconds) at which the delivery was signed. Always sent — also use it to reject stale replays.
X-NagorikPay-Event What triggered this delivery: completed, pending, failed or auto-completed.
X-NagorikPay-Signature sha256=<hex>. Always sent. Signed with the brand's Webhook Secret when one has been generated, otherwise with the brand API key.

What exactly is signed

The signed message is the timestamp, a literal dot, and then the raw request body exactly as it was sent (the form-urlencoded string, not a re-encoded copy of the parsed fields). Compute:

signature = HMAC_SHA256( key = webhook_secret, message = timestamp + "." + raw_body )
header    = "X-NagorikPay-Signature: sha256=" + hex(signature)

The signing key is your brand's Webhook Secret if you have generated one, and your brand API key if you have not. The Webhook Secret is the recommended setup: it is a separate value, so a leaked API key can no longer be used to forge webhooks. Rotating it immediately invalidates the old one — the previous secret stops verifying the moment you rotate.

Verifying in PHP


<?php
// Your brand's Webhook Secret if you generated one (merchant panel →
// Brands → Webhook Secret), otherwise your brand API key.
$secret    = 'YOUR_WEBHOOK_SECRET_OR_API_KEY';
$timestamp = $_SERVER['HTTP_X_NAGORIKPAY_TIMESTAMP'] ?? '';
$given     = $_SERVER['HTTP_X_NAGORIKPAY_SIGNATURE'] ?? '';
$rawBody   = file_get_contents('php://input');   // raw bytes — do not rebuild the string

// 1) Refuse replayed or stale deliveries (older than 5 minutes).
if ($timestamp === '' || abs(time() - (int) $timestamp) > 300) {
    http_response_code(401);
    exit('Stale webhook');
}

// 2) A signature is always sent — a missing one means the request is not ours.
if ($given === '') {
    http_response_code(401);
    exit('Missing signature');
}

// 3) Constant-time compare — never use == for signatures.
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
if (!hash_equals($expected, $given)) {
    http_response_code(401);
    exit('Invalid signature');
}

// 4) Signature is good — reply 2xx first, then do the real work.
http_response_code(200);
parse_str($rawBody, $payload);
// ... fulfil the order, using $payload['transactionId'] / $payload['status']
                  

Verifying in Node.js


const crypto = require('crypto');

const secret    = process.env.NAGORIKPAY_WEBHOOK_SECRET || process.env.NAGORIKPAY_API_KEY;
const timestamp = req.header('X-NagorikPay-Timestamp') || '';
const given     = req.header('X-NagorikPay-Signature') || '';
const rawBody   = req.rawBody;   // the exact bytes — keep it, do not JSON.stringify(req.body)

if (!timestamp || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
  return res.status(401).send('Stale webhook');
}
if (!given) {
  return res.status(401).send('Missing signature');
}

const expected = 'sha256=' + crypto
  .createHmac('sha256', secret)
  .update(timestamp + '.' + rawBody)
  .digest('hex');

// timingSafeEqual throws on length mismatch, so compare lengths first.
const ok = given.length === expected.length &&
           crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));

if (!ok) return res.status(401).send('Invalid signature');

res.sendStatus(200);   // acknowledge before doing the heavy work
                  
The two things that break signature checks most often: (1) verifying against a re-serialised body instead of the raw bytes — the hash will never match; (2) checking the signature with == instead of a constant-time compare. Also remember that when you generate a Webhook Secret the signing key changes, so update your verifier in the same step — until then your API key remains the signing key and your existing code keeps working.

Modules & Plugins

Ready-made modules and plugins to drop Nagorikpay into your favourite platform. Download, configure your API keys and start accepting payments.

WordPress WooCommerce Plugin

See Setup Video: YouTube Video

WHMCS Module

See Setup Video: How To Setup Video here

Sketchware SWB

Enhance your App with our payment gateway integration module.

SMM Panel Module

See Setup Video: Video here

Modified SMM Panel Module

See Setup Video: Video here

Mobile App Download

See Setup Video: How To Setup App Video here

Active CMS Module

Seamlessly connect nagorikPay to Active CMS websites for managing payments with ease. Perfect for businesses using Active CMS to run their online services. Video here

Market BOB Module

Enhance Market BOB platforms by integrating our secure payment solution. Accept payments instantly and automate your order management workflow. Video here

Laravel Module

Nagorikpay provides a ready-made Laravel module for developers. Easily integrate payment gateway APIs into your Laravel projects with minimal coding effort. Video here