GatePay · Developers

Payment Verification API

Public endpoint that lets partner sites confirm a payment matches a transaction recorded in GatePay, and stamp it with the verifier's business identity.

Overview

GatePay exposes two primary flows and supporting endpoints for partner sites:

  1. Submit — your server POSTs a transaction when an order is placed. It lands in GatePay as unverified.
  2. Verify callback (GatePay → your site) — a GatePay admin selects unverified transactions and clicks Trigger verify. GatePay groups them by business, POSTs each group to your configured callback_url, and on a 2xx response stamps them as verified.
  3. Verify on demand (your site → GatePay) — your server can also call this endpoint directly to check a single transaction's status.
  4. Refund — admin-initiated through the dashboard. A verified transaction can be refunded via the payment gateway. Partners receive a callback with event: "reimbursed".

All endpoints require an API key via the Authorization: Bearer header (except health & OpenAPI spec). Manage keys from Admin → API Keys. The signing secret is auto-generated on creation and can be re-viewed anytime by clicking the eye icon.

POST — Submit transaction

Creates an unverified transaction in GatePay. Call this when an order is placed on your end. Safe to retry with the Idempotency-Key header.

Endpoint

http
POST https://pay.darvizlabs.com/api/v1/public/transactions/submit

Headers

http
Authorization: Bearer YOUR_API_KEY

Request body

FieldTypeRequiredNotes
transaction_refstring (1–120)YesYour unique transaction ID.
amountnumber ≥ 0YesPayment amount.
currencystring (≤8)NoDefaults to 'BDT'.
occurred_atISO 8601 datetimeNoDefaults to now().
methodstring (≤40)Noe.g. 'bkash', 'card', 'nagad'.
business_namestring (1–160)No*Falls back to the API key's business_name.
external_user_idstring (≤160)NoYour end-user ID for cross-reference.
sourcestring (≤160)NoFree-form tag, e.g. 'web-checkout'.
notesstring (≤2000)NoFree-form notes.

Example

json
{
  "transaction_ref": "INV-2026-00482",
  "amount": 1499,
  "currency": "BDT",
  "method": "bkash",
  "business_name": "Nerdy",
  "external_user_id": "user_8821",
  "source": "web-checkout",
  "occurred_at": "2026-05-27T10:00:00Z"
}

POST — Verify transaction

Checks if a submitted transaction matches a known ref. Returns the transaction details if found. Use when you need to confirm a specific payment on demand.

Endpoint

http
POST https://pay.darvizlabs.com/api/v1/public/transactions/verify

Headers

http
Authorization: Bearer YOUR_API_KEY

Request body

FieldTypeRequiredNotes
transaction_refstring (1–120)YesTransaction ID to look up. Case-insensitive.
business_namestring (1–160)YesMust match the API key's business_name.
external_user_idstring (≤160)NoYour internal user ID, stored on the transaction.
dateISO date / datetimeNoIf set, must match the transaction's UTC day.
amountnumber ≥ 0NoIf set, must equal recorded amount exactly.
sourcestring (≤160)NoFree-form audit label.

Example

json
{
  "transaction_ref": "INV-2026-00482",
  "business_name": "Nerdy",
  "external_user_id": "user_8821",
  "date": "2026-05-27",
  "amount": 1499,
  "source": "web-checkout"
}

POST — Review transaction

Submits a client-side review (confirmed amount + note) against an existing transaction. Use when you want your customer or internal team to confirm or annotate a recorded transaction.

Endpoint

http
POST https://pay.darvizlabs.com/api/v1/public/transactions/review

Headers

http
Authorization: Bearer YOUR_API_KEY

Request body

FieldTypeRequiredNotes
transaction_idstringYesTransaction ID returned by the submit endpoint.
amountnumber ≥ 0YesConfirmed amount for the transaction.
notestring (≤2000)YesReview note or feedback.

Example

json
{
  "transaction_id": "jf3m2k9x1p",
  "amount": 1499,
  "note": "Payment confirmed by customer — project setup fee."
}

Verify callback (GatePay → your site)

When an admin clicks Trigger verify in the dashboard, GatePay groups the selected transactions by business name, finds the matching API key's callback_url, and POSTs each group to that URL. Your 2xx response is the verification — nothing more needed.

What is a callback URL?

A callback URL is an HTTP endpoint on your own server that GatePay calls to confirm a batch of transactions. Example: https://api.nerdy.com/gatekeepr/verify. It must be HTTPS, must return a 2xx to confirm verification, and you can verify the request via the X-GatePay-Signature HMAC header.

Why verify fails from the admin

If your API key has no callback_url set, or the URL is unreachable (like https://example.com/verify), the admin verification will show skipped_no_callback or callback_timeout. Set a real callback URL on your API key in Admin → API Keys.

Request GatePay sends

http
POST <your callback_url>
Content-Type: application/json
X-GatePay-Signature: sha256=<hex hmac>
User-Agent: GatePay-Verify/1.0

{
  "business_name": "Nerdy",
  "sent_at": "2026-05-27T16:30:00.000Z",
  "transactions": [
    {
      "transaction_ref": "INV-2026-00482",
      "amount": 1499.00,
      "currency": "BDT",
      "occurred_at": "2026-05-27T10:00:00.000Z",
      "method": "bkash",
      "external_user_id": "user_8821",
      "source": "web-checkout"
    }
  ]
}

What your endpoint should do

  1. Verify the X-GatePay-Signature HMAC (optional but recommended).
  2. For each transaction, look it up in your own DB and confirm it matches a real order.
  3. Respond 2xx if everything checks out — GatePay stamps the batch as verified.
  4. Respond with a non-2xx status and a JSON error body on failure.
  5. You do not need to call the verify endpoint from inside the callback.

GET — Health check

Lightweight endpoint to verify the API is operational. No auth required.

http
GET https://pay.darvizlabs.com/api/v1/public/health
json
{"status":"ok"}

POST — Request refund

Request a refund for a verified transaction. The client specifies who should receive the refund (name and number) and the amount. Refunds are processed by GatePay and require the transaction to be in verified status.

Endpoint

http
POST https://pay.darvizlabs.com/api/v1/public/transactions/refund

Headers

http
Authorization: Bearer YOUR_API_KEY

Request body

FieldTypeRequiredNotes
transaction_refstring (1–120)YesTransaction ID to refund. Must belong to the same business.
amountnumber > 0YesRefund amount. Cannot exceed the original transaction amount.
methodstring (1–40)YesRefund method: 'bKash', 'Nagad', 'Rocket', 'bank_transfer', 'other'.
receiver_namestring (1–256)YesFull name of the person receiving the refund.
receiver_numberstring (1–64)YesAccount or phone number of the receiver.
notesstring (≤2000)NoFree-form notes about the refund.

Example

json
{
  "transaction_ref": "INV-2026-00482",
  "amount": 1499,
  "method": "bKash",
  "receiver_name": "Rafid Mahim",
  "receiver_number": "01712345678",
  "notes": "Customer requested full refund"
}

Refund lifecycle

Refunds can be initiated by clients via the API or by admins through the dashboard. The flow is: initiate → process via payment gateway → complete or fail.

Only verified transactions can be refunded. The receiver name and number are required — these specify who receives the refunded amount.

Lifecycle

  1. Initiate — admin clicks "Initiate Refund" on a verified transaction, enters amount, method, and optional reference. Creates a refund record with status pending.
  2. Process — system processes the refund through the payment gateway. Refund status moves to processing.
  3. Complete — on gateway confirmation, refund status becomes completed and the transaction status moves to reimbursed.
  4. Cancel — admin can cancel a pending or processing refund. Cancellation is not allowed once completed.

Refund statuses

StatusMeaning
pendingRefund initiated, waiting for gateway processing
processingGateway has accepted the refund request
completedRefund succeeded — transaction is now reimbursed
failedGateway rejected the refund
cancelledAdmin cancelled before completion

What gets logged

  • Every refund action is recorded in the statusHistory table with from/to status and notes.
  • On completion, the transaction stores: reimbursedAt, reimbursementAmount, reimbursementRef, and reimbursementMethod.
  • Partners receive a callback with event: "reimbursed" when the transaction status changes.

Error codes

StatusResponseMeaning
201{"received":true,"status":"unverified"}Submit success
201{"refund_id":"...","status":"pending",...}Refund requested
200{"verified":true,"transaction":{...}}Transaction matches
200{"verified":false,"reason":"not_found"}No matching transaction
200{"verified":false,"reason":"date_mismatch"}Date doesn't match
200{"verified":false,"reason":"amount_mismatch"}Amount doesn't match
400{"error":"invalid_body","issues":[...]}Zod validation failed
400{"error":"invalid_json"}Body is not valid JSON
401{"error":"missing_api_key"}No Authorization header
401{"error":"invalid_api_key"}Token unknown or revoked
404{"error":"transaction_not_found"}Transaction doesn't exist or belongs to another business
409{"error":"duplicate_ref"}Ref already exists (submit)
409{"error":"transaction_not_verified"}Transaction must be verified before refund
413{"error":"body_too_large"}Body exceeds 10 KB
429{"error":"rate_limited"}IP rate limit hit (30/60 req/min)
429{"error":"key_rate_limited"}Key rate limit hit (100 req/min)
500{"verified":false,"reason":"lookup_error"}Server / DB error

Every response includes an x-request-id header. Include this when reporting issues.

Security

  • All endpoints require HTTPS. HTTP requests are rejected.
  • API keys are hashed with SHA-256 before storage. Revocation is immediate.
  • Tenant isolation: keys can only verify transactions matching their own business_name.
  • Rate limits: verify 30 req/min/IP, submit 60 req/min/IP, 100 req/min per key.
  • Request body limited to 10 KB. CSRF enforced when Origin/Referer is present.
  • Responses include Strict-Transport-Security and X-Content-Type-Options.
  • Callback URLs must use HTTPS. Callbacks have a 15-second timeout.
  • Callback signatures use HMAC-SHA256 with your key's signing_secret.

TypeScript module

A reusable GatePay client class you can drop into any Node.js/TypeScript project.

typescript
// gatepay.ts
import crypto from "node:crypto";

interface GatePayConfig {
  apiKey: string;
  signingSecret: string;
  baseUrl?: string;
}

class GatePayError extends Error {
  constructor(public status: number, public code: string, public details?: any) {
    super(code);
    this.name = "GatePayError";
  }
}

class GatePay {
  private baseUrl: string;
  private apiKey: string;
  private signingSecret: string;

  constructor(config: GatePayConfig) {
    this.apiKey = config.apiKey;
    this.signingSecret = config.signingSecret;
    this.baseUrl = config.baseUrl ?? "https://pay.darvizlabs.com/api/v1/public";
  }

  private async request<T>(path: string, body: unknown): Promise<T> {
    const res = await fetch(`${this.baseUrl}${path}`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${this.apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });
    const data = await res.json();
    if (!res.ok) throw new GatePayError(res.status, data.error ?? "unknown_error");
    return data as T;
  }

  async submit(payload: any) { return this.request("/transactions/submit", payload); }
  async verify(payload: any) { return this.request("/transactions/verify", payload); }
  async refund(payload: any) { return this.request("/transactions/refund", payload); }
  async review(payload: any) { return this.request("/transactions/review", payload); }
}

// Usage
const gp = new GatePay({
  apiKey: process.env.GATEKEEPR_API_KEY!,
  signingSecret: process.env.GATEKEEPR_SIGNING_SECRET!,
});
const { status } = await gp.submit({
  transaction_ref: "INV-2026-00482",
  amount: 1499.00,
  currency: "BDT",
  method: "bkash",
  business_name: "Nerdy",
});
console.log(status); // "unverified"

Python module

A Python client using httpx.

python
# gatepay.py
import hmac, hashlib
from dataclasses import dataclass
import httpx

class GatePayError(Exception):
    def __init__(self, status: int, code: str, details: dict = None):
        self.status = status
        self.code = code
        self.details = details
        super().__init__(code)

@dataclass
class GatePayConfig:
    api_key: str
    signing_secret: str
    base_url: str = "https://pay.darvizlabs.com/api/v1/public"

class GatePay:
    def __init__(self, config: GatePayConfig):
        self.config = config
        self._client = httpx.Client(base_url=self.config.base_url)

    def _headers(self):
        return {"Authorization": f"Bearer {self.config.api_key}", "Content-Type": "application/json"}

    def submit(self, transaction_ref: str, amount: float, **kwargs):
        payload = {"transaction_ref": transaction_ref, "amount": amount, **kwargs}
        resp = self._client.post("/transactions/submit", json=payload, headers=self._headers())
        data = resp.json()
        if not resp.is_success:
            raise GatePayError(resp.status_code, data.get("error", "unknown"))
        return data

    def verify(self, transaction_ref: str, business_name: str):
        resp = self._client.post("/transactions/verify", json={
            "transaction_ref": transaction_ref, "business_name": business_name,
        }, headers=self._headers())
        return resp.json()

# Usage
gp = GatePay(GatePayConfig(api_key="gk_xxxx", signing_secret="xxxx"))
result = gp.submit("INV-001", 1499.00, business_name="Nerdy", method="bkash")
print(result["status"])  # "unverified"
verify = gp.verify("INV-001", "Nerdy")
print(verify["verified"])  # True/False

Express.js example

Payment flow with checkout, callback handling, and HMAC verification.

typescript
import { Router } from "express";
import crypto from "node:crypto";

const router = Router();

router.post("/checkout", async (req, res) => {
  const { amount, userId } = req.body;
  const ref = `INV-${Date.now()}`;
  const submit = await fetch("https://pay.darvizlabs.com/api/v1/public/transactions/submit", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.GATEKEEPR_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      transaction_ref: ref, amount, currency: "BDT",
      method: "bkash", business_name: "Nerdy", external_user_id: userId,
    }),
  }).then(r => r.json());
  res.json({ ref, gatepay_id: submit.id });
});

router.post("/gatepay/callback", async (req, res) => {
  const signature = req.headers["x-gatepay-signature"];
  const rawBody = JSON.stringify(req.body);
  const expected = crypto.createHmac("sha256", process.env.GATEKEEPR_SIGNING_SECRET!)
    .update(rawBody).digest("hex");
  if (`sha256=${expected}` !== signature) return res.status(401).json({ error: "invalid signature" });
  for (const tx of req.body.transactions) console.log(`Verified: ${tx.transaction_ref}`);
  res.sendStatus(200);
});

export { router as paymentRouter };

// Integration checklist:
// ☐ Set GATEKEEPR_API_KEY and GATEKEEPR_SIGNING_SECRET
// ☐ Add callback URL to API key
// ☐ Call POST /submit on order placement
// ☐ Handle callback with HMAC verification

Subscription module

Recurring monthly billing via GatePay\'s customer-facing pay page. Payments are auto-verified with verifiedSource: "subscription".

typescript
class GatePaySubscription {
  private baseUrl = "https://pay.darvizlabs.com";
  getPayLink(payCode: string): string {
    return `${this.baseUrl}/pay/${payCode}`;
  }
}

// 1. Admin creates project with billing config in dashboard
// 2. Share link with client
const link = new GatePaySubscription().getPayLink("DNKX4U");
// → https://pay.darvizlabs.com/pay/DNKX4U

// 3. Client opens link, selects month, pays via bKash
// 4. Transaction auto-verified as subscription payment
// 5. Confirmation email sent to client automatically
Questions? Contact the GatePay team.