Public endpoint that lets partner sites confirm a payment matches a transaction recorded in GatePay, and stamp it with the verifier's business identity.
GatePay exposes two primary flows and supporting endpoints for partner sites:
unverified.callback_url, and on a 2xx response stamps them as verified.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.
Creates an unverified transaction in GatePay. Call this when an order is placed on your end. Safe to retry with the Idempotency-Key header.
POST https://pay.darvizlabs.com/api/v1/public/transactions/submitAuthorization: Bearer YOUR_API_KEY| Field | Type | Required | Notes |
|---|---|---|---|
transaction_ref | string (1–120) | Yes | Your unique transaction ID. |
amount | number ≥ 0 | Yes | Payment amount. |
currency | string (≤8) | No | Defaults to 'BDT'. |
occurred_at | ISO 8601 datetime | No | Defaults to now(). |
method | string (≤40) | No | e.g. 'bkash', 'card', 'nagad'. |
business_name | string (1–160) | No* | Falls back to the API key's business_name. |
external_user_id | string (≤160) | No | Your end-user ID for cross-reference. |
source | string (≤160) | No | Free-form tag, e.g. 'web-checkout'. |
notes | string (≤2000) | No | Free-form notes. |
{
"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"
}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.
POST https://pay.darvizlabs.com/api/v1/public/transactions/verifyAuthorization: Bearer YOUR_API_KEY| Field | Type | Required | Notes |
|---|---|---|---|
transaction_ref | string (1–120) | Yes | Transaction ID to look up. Case-insensitive. |
business_name | string (1–160) | Yes | Must match the API key's business_name. |
external_user_id | string (≤160) | No | Your internal user ID, stored on the transaction. |
date | ISO date / datetime | No | If set, must match the transaction's UTC day. |
amount | number ≥ 0 | No | If set, must equal recorded amount exactly. |
source | string (≤160) | No | Free-form audit label. |
{
"transaction_ref": "INV-2026-00482",
"business_name": "Nerdy",
"external_user_id": "user_8821",
"date": "2026-05-27",
"amount": 1499,
"source": "web-checkout"
}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.
POST https://pay.darvizlabs.com/api/v1/public/transactions/reviewAuthorization: Bearer YOUR_API_KEY| Field | Type | Required | Notes |
|---|---|---|---|
transaction_id | string | Yes | Transaction ID returned by the submit endpoint. |
amount | number ≥ 0 | Yes | Confirmed amount for the transaction. |
note | string (≤2000) | Yes | Review note or feedback. |
{
"transaction_id": "jf3m2k9x1p",
"amount": 1499,
"note": "Payment confirmed by customer — project setup fee."
}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.
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.
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"
}
]
}X-GatePay-Signature HMAC (optional but recommended).2xx if everything checks out — GatePay stamps the batch as verified.Lightweight endpoint to verify the API is operational. No auth required.
GET https://pay.darvizlabs.com/api/v1/public/health{"status":"ok"}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.
POST https://pay.darvizlabs.com/api/v1/public/transactions/refundAuthorization: Bearer YOUR_API_KEY| Field | Type | Required | Notes |
|---|---|---|---|
transaction_ref | string (1–120) | Yes | Transaction ID to refund. Must belong to the same business. |
amount | number > 0 | Yes | Refund amount. Cannot exceed the original transaction amount. |
method | string (1–40) | Yes | Refund method: 'bKash', 'Nagad', 'Rocket', 'bank_transfer', 'other'. |
receiver_name | string (1–256) | Yes | Full name of the person receiving the refund. |
receiver_number | string (1–64) | Yes | Account or phone number of the receiver. |
notes | string (≤2000) | No | Free-form notes about the refund. |
{
"transaction_ref": "INV-2026-00482",
"amount": 1499,
"method": "bKash",
"receiver_name": "Rafid Mahim",
"receiver_number": "01712345678",
"notes": "Customer requested full refund"
}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.
pending.processing.completed and the transaction status moves to reimbursed.| Status | Meaning |
|---|---|
pending | Refund initiated, waiting for gateway processing |
processing | Gateway has accepted the refund request |
completed | Refund succeeded — transaction is now reimbursed |
failed | Gateway rejected the refund |
cancelled | Admin cancelled before completion |
statusHistory table with from/to status and notes.reimbursedAt, reimbursementAmount, reimbursementRef, and reimbursementMethod.event: "reimbursed" when the transaction status changes.| Status | Response | Meaning |
|---|---|---|
| 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.
business_name.Strict-Transport-Security and X-Content-Type-Options.signing_secret.A reusable GatePay client class you can drop into any Node.js/TypeScript project.
// 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"A Python client using httpx.
# 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/FalsePayment flow with checkout, callback handling, and HMAC verification.
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 verificationRecurring monthly billing via GatePay\'s customer-facing pay page. Payments are auto-verified with verifiedSource: "subscription".
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