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:

  1. /newapp → select your bot
  2. Web App URL: your Mini App URL (e.g. https://yourshop.com)
  3. 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