sk_live_xxx…). That single key does everything: create payments, check status, refund, list transactions, see balance.No webhook setup. No HTTPS endpoint. No signing secret. You don't need to expose any URL to the internet — even shared hosting (cPanel) works. Status updates come back to you via a simple cron job that reads our events feed.
How payments flow
[api_url not set]/v1/sessions[api_url not set]/v1/eventsThese are our servers. You don't change them. Same URL for every merchant.
yoursite.com)Anywhere you see https://yoursite.com/..., replace it with your own domain. These are pages on your site we redirect the customer back to:
- success_url→ after successful payment
- cancel_url→ if customer clicks "Cancel"
- failed_url→ if the payment fails
⚠️ Don't trust success_url alone — the customer can manipulate the URL. Always confirm via the events feed (polling) or by calling GET /v1/sessions/{id}.
How the whole system works — in detail
One API key runs everything. Below is a step-by-step breakdown — what files to put on your website, what each file does, and how the API talks to your server.
Customer Your Website My Pay ([api_url not set])
│ │ │
│ ── Click "Pay Now" ───▶ │ │
│ │ ── POST /v1/sessions ──────▶ │ (Bearer sk_live_…)
│ │ ◀──── { checkout_url } ───── │
│ ◀── 302 redirect ────── │ │
│ │
│ ────── Pays (crypto / mobile banking) ───────────────▶ │
│ │
│ ◀── redirect → success_url / failed_url / cancel_url ──│
│ │ │
│ │ Cron: GET /v1/events ──────▶ │ (every 60 sec)
│ │ ◀── [ session.approved ] ─── │
│ │ │
│ │ Update order in your DB │
│ │ (paid / failed) │Just one variable —
MYPAY_SECRET=sk_live_xxxxxxxxxxxxxxxx⚠️ Never put this in frontend code. Server-side files only.
Runs when the customer clicks "Pay Now". Takes the amount + order_id, calls our API, and redirects the customer to checkout_url.
POST [api_url not set]/v1/sessions
Authorization: Bearer $MYPAY_SECRET
{ "amount": 1500, "currency": "BDT",
"reference_id": "ORDER-12345",
"success_url": "https://yoursite.com/success",
"cancel_url": "https://yoursite.com/cancel",
"failed_url": "https://yoursite.com/failed",
"metadata": { "order_id": 12345 } }⚠️ Take checkout_url from the response and redirect the customer there.
Where the customer lands after payment. Just simple "Thank you" / "Try again" pages.
⚠️ Do NOT update the DB here — customers can manipulate the URL. The real confirmation comes from the cron.
Runs every 60 seconds. Fetches new events since the last event id and updates your DB.
GET [api_url not set]/v1/events?since=<last_event_id>
Authorization: Bearer $MYPAY_SECRET
# response:
{ "events": [
{ "id": 892, "type": "session.approved",
"session_id": "ses_…", "reference_id": "ORDER-12345",
"amount": 1500, "status": "paid" }
],
"last_id": 892 }⚠️ Save last_id in DB or a file — pass it as since= on the next call.
In your hosting panel, schedule a cron that runs the poll script every minute.
# cPanel / crontab example
* * * * * /usr/bin/php /home/user/public_html/poll-mypay.php >/dev/null 2>&1⚠️ On Vercel use the crons block in vercel.json; on Cloudflare use wrangler cron — same idea.
| Endpoint | When you use it | What it returns |
|---|---|---|
| POST /v1/sessions | When an order is placed — to create a checkout page | checkout_url + session_id |
| GET /v1/sessions/{id} | To check the live status of a single order at any time | status: pending / paid / failed / expired |
| GET /v1/events?since= | From your cron — to fetch every new update in one call | events[] + last_id |
| GET /v1/transactions | To build a dashboard or reconciliation report | transaction list (paged) |
| POST /v1/refunds | To issue a refund from your admin panel | refund_id + status |
| GET /v1/balance | To display available balance | { available, pending, currency } |
These 6 endpoints together cover the entire payment lifecycle — create, status, events, list, refund, balance. Every call uses the same header: Authorization: Bearer sk_live_…
Webhooks mean we push HTTP requests to your server. That requires a public HTTPS URL, signing-secret verification, and retry handling — painful on shared hosting, cPanel, or localhost.
Polling means you come to us and pull events. No public URL needed, no signature checks — just the same API key. A single cron job is enough. It works on any host, even ones that block inbound webhooks.
- ✅ No signing secret to manage
- ✅ No HTTPS or public endpoint required
- ✅ No missed events — since=<last_id> replays everything
- ✅ No duplicate-delivery problems — same data every time you read
- One API secret key (generated from the dashboard)
- One server-side "create-payment" route that calls
/v1/sessions - One cron job that pulls updates from
/v1/events
That's it. No SDK to install, no library required, no webhook URL to set up. Copy/paste the ready-made code for your platform below.
Before you start
https://myshop.com). For Live keys you must verify domain ownership; Test keys work without it.sk_live_… (or sk_test_… for sandbox). Paste it into your .env / config.php as MYPAY_SECRET.⚠️ The secret is shown only ONCE. Never paste it into browser/frontend code.GET /v1/events?since=<last_id> using the same secret key. You'll save the last last_event_id somewhere (DB, Redis, a file). The polling script below handles this — copy/paste.The 3 pieces in your app
Whatever language/framework you use, you'll create these three things:
POST /pay. Calls our API, redirects customer to checkout_url.poll-mypay.php running every 60s via cron. Calls /v1/events and marks orders paid./success, /cancel, /failed. Just simple thank-you / try-again pages.Setup for your platform
config.php, pay.php, poll-mypay.php, success.php, cancel.php, failed.php.pay.php.crontab -e) add: * * * * * /usr/bin/php /path/to/poll-mypay.phpFiles to create
config.php— API key — copy from Dashboard → API Keys<?php
// 👉 Get this from Dashboard → API Keys → Generate → copy "sk_live_..."
define('GATEWAY_URL', '[api_url not set]');
define('MYPAY_SECRET', 'sk_live_xxxxxxxxxxxxxxxx');
// Use sk_test_... during development (sandbox mode)checkout.html— Pay Now button<form method="POST" action="/pay.php">
<input type="hidden" name="order_id" value="12345">
<input type="hidden" name="amount" value="1500">
<button type="submit">Pay Now</button>
</form>pay.php— Creates payment session and redirects<?php
require 'config.php';
$orderId = $_POST['order_id'];
$amount = $_POST['amount'];
$ch = curl_init(GATEWAY_URL . '/v1/sessions');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . MYPAY_SECRET,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => (float) $amount,
'currency' => 'BDT',
'reference_id' => 'ORDER-' . $orderId,
'success_url' => 'https://yoursite.com/success.php',
'cancel_url' => 'https://yoursite.com/cancel.php',
'failed_url' => 'https://yoursite.com/failed.php',
'metadata' => ['order_id' => $orderId],
]),
]);
$res = json_decode(curl_exec($ch), true);
header('Location: ' . $res['checkout_url']);
exit;poll-mypay.php— Cron job — runs every minute to fetch payment events<?php
require 'config.php';
$lastId = (int) (@file_get_contents(__DIR__.'/last_event_id.txt') ?: 0);
$ch = curl_init(GATEWAY_URL . "/v1/events?since=$lastId&limit=100");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . MYPAY_SECRET],
]);
$res = json_decode(curl_exec($ch), true);
foreach ($res['data'] ?? [] as $ev) {
if ($ev['event_type'] === 'session.approved') {
$orderId = $ev['payload']['reference_id'] ?? null;
// 👉 UPDATE orders SET status='paid' WHERE id='$orderId'
}
}
file_put_contents(__DIR__.'/last_event_id.txt', (string)($res['last_event_id'] ?? $lastId));Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| 401 invalid_api_key | Wrong/revoked key | Generate a new key in /app/api-keys |
| No redirect happens | checkout_url empty in response | Log the JSON response from /v1/sessions |
| Order stays unpaid | Polling cron not running | Check crontab / cPanel cron / Vercel cron logs |
| Same event processed twice | last_event_id not persisted | Save it in DB transactionally with the order update |
| Success page reached but unpaid | User manipulated URL | Trust the events feed, not success_url |
| Test keys can't see live data | Sandbox isolation (by design) | Use sk_live_… for production data |
Ready to start?
Generate your test API key and try it in 2 minutes.