API Reference
Base URL: https://fastpayglobal.app
Authentication
Send your publishable key via the x-public-key header on every request to /api/public/*. Keep your webhook signing secret on the server only — it is used to verify incoming webhook signatures.
Create a payment
http
POST https://fastpayglobal.app/api/public/create-payment
Headers:
content-type: application/json
x-public-key: pk_live_xxxxxxxxxxxx
Body:
{
"amount": 500, // required, > 0 (in BDT, not cents)
"currency": "BDT", // default "BDT"
"orderId": "ORDER-12345", // required, unique per brand
"reference": "INV-001", // optional
"customer": { // optional
"name": "John Doe",
"email": "john@example.com",
"phone": "01710000000"
},
"metadata": { "any": "json" } // optional, returned in webhook
}
Response 200:
{
"id": "uuid", // payment id
"status": "pending",
"amount": 500,
"currency": "BDT",
"expiresAt": "2026-05-13T10:15:00Z",
"hostedUrl": "https://fastpayglobal.app/pay/<id>" // open this in browser / Telegram
}Get a payment
http
GET https://fastpayglobal.app/api/public/payment/<id>
Response 200:
{
"payment": {
"id": "uuid",
"orderId": "ORDER-12345",
"reference": "INV-001",
"amount": 500,
"currency": "BDT",
"status": "pending" | "awaiting_confirmation" | "paid" | "expired" | "failed",
"method": "bkash" | "nagad" | "crypto" | null,
"trxId": "TRX-XXX" | null,
"expiresAt": "ISO 8601"
},
"brand": { "id", "name", "logo_url", "theme_color" },
"methods": [ /* enabled payment methods */ ],
"gateways": [ /* gateway display config */ ]
}Webhook payload & signature
http
POST <your brand webhook_url>
Headers:
content-type: application/json
x-paysol-event: payment.succeeded
x-paysol-timestamp: 1715600000
x-paysol-signature: t=1715600000,v1=<hex hmac sha256>
Body:
{
"event": "payment.succeeded",
"paymentId": "uuid",
"orderId": "ORDER-12345",
"reference": "INV-001",
"amount": 500,
"currency": "BDT",
"method": "bkash",
"provider": "bkash",
"trxId": "TRX-XYZ",
"paidAt": "2026-05-13T10:00:00Z",
"customer": { "name": "John", "email": "...", "phone": "..." },
"metadata": { /* whatever you sent at create time */ }
}
Signature scheme:
signed_payload = "<timestamp>.<raw_request_body>"
signature = hex( HMAC_SHA256(webhook_signing_secret, signed_payload) )
Sent as: x-paysol-signature: t=<timestamp>,v1=<signature>
Verify in Node.js:
import crypto from "crypto";
const sigHeader = req.headers["x-paysol-signature"]; // "t=...,v1=..."
const parts = Object.fromEntries(sigHeader.split(",").map(p => p.split("=")));
const expected = crypto
.createHmac("sha256", process.env.FASTPAYGLOBAL_WEBHOOK_SECRET)
.update(parts.t + "." + rawBody) // rawBody = the unparsed string
.digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
// Also reject if Math.abs(Date.now()/1000 - Number(parts.t)) > 300
Retries: up to 6 attempts with backoff 30s, 2m, 10m, 1h, 6h, 24h.
Respond with 2xx within 15s to acknowledge.Telegram Mini App + Node.js / React
Three pieces are required: a backend endpoint that calls FastPayGlobal with your secret public key, a React component inside the Mini App that opens the hosted checkout, and a webhook receiver that updates your orders.
1. Backend — create payment
javascript
// Node.js / Express — create payment from your server
import express from "express";
const app = express();
app.use(express.json());
app.post("/api/checkout", async (req, res) => {
const { orderId, amount, customer } = req.body;
const r = await fetch("https://fastpayglobal.app/api/public/create-payment", {
method: "POST",
headers: {
"content-type": "application/json",
"x-public-key": process.env.FASTPAYGLOBAL_PUBLIC_KEY, // pk_live_...
},
body: JSON.stringify({ orderId, amount, customer }),
});
if (!r.ok) return res.status(502).json({ error: await r.text() });
const data = await r.json();
// { id, hostedUrl, expiresAt, ... }
res.json({ payUrl: data.hostedUrl, paymentId: data.id });
});2. React Mini App — open checkout
jsx
// React Telegram Mini App — open hosted checkout
// public/index.html must include:
// <script src="https://telegram.org/js/telegram-web-app.js"></script>
import { useEffect } from "react";
export default function Checkout({ order }) {
const tg = window.Telegram.WebApp;
const startPayment = async () => {
const r = await fetch("/api/checkout", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
orderId: order.id,
amount: order.total,
customer: {
name: tg.initDataUnsafe?.user?.first_name,
phone: order.phone,
},
}),
});
const { payUrl } = await r.json();
// Open inside Telegram (in-app browser)
tg.openLink(payUrl);
// Optionally close the Mini App after redirect
// tg.close();
};
useEffect(() => {
tg.ready();
tg.expand();
tg.MainButton.setText(`💳 Pay ৳${order.total}`);
tg.MainButton.show();
tg.MainButton.onClick(startPayment);
return () => tg.MainButton.offClick(startPayment);
}, [order]);
return <div>Order #{order.id} — ৳{order.total}</div>;
}3. Webhook receiver — verify & mark paid
javascript
// Node.js / Express — webhook receiver with signature verification
import express from "express";
import crypto from "crypto";
const app = express();
// IMPORTANT: use raw body for signature verification
app.post(
"/api/fastpayglobal-webhook",
express.raw({ type: "application/json" }),
async (req, res) => {
const rawBody = req.body.toString("utf8");
const sigHeader = req.headers["x-paysol-signature"] || "";
const parts = Object.fromEntries(
String(sigHeader).split(",").map((p) => p.split("="))
);
// 1. Replay protection (5 minute window)
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) {
return res.status(401).send("stale");
}
// 2. Signature check
const expected = crypto
.createHmac("sha256", process.env.FASTPAYGLOBAL_WEBHOOK_SECRET)
.update(parts.t + "." + rawBody)
.digest("hex");
if (
!parts.v1 ||
!crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected))
) {
return res.status(401).send("bad signature");
}
// 3. Process the event
const event = JSON.parse(rawBody);
if (event.event === "payment.succeeded") {
// Mark order paid in your DB using event.orderId / event.trxId
}
res.status(200).send("ok");
}
);BotFather setup:
/newapp→ select your bot- Web App URL: your Mini App URL (e.g.
https://yourshop.com) - Set Menu Button → text "🛒 Shop", URL same as above
Errors
text
Standard error shape:
{ "error": "human readable message" }
Common HTTP statuses:
400 invalid body / validation error
401 missing or invalid x-public-key
402 brand plan expired
403 brand disabled or IP not whitelisted
404 payment not found
409 order id already used / trx id reused
410 payment expired
429 rate limited
5xx server error — safe to retry with same orderId