KantoPay API
Take payments with GCash, Maya, QRPh and cards, then deliver in-game perks to your Minecraft server the moment an order settles — all from one REST API.
Get startedThe KantoPay API is a fast, idempotent payments-and-fulfilment service built for Southeast-Asian game stores. It creates local-rail payment links, tracks them to settlement, and routes the right command to the right server — while protecting you from double-delivery and double-spend. To know where to begin, refer to Get started.
Every request is authenticated with your API key (as a Bearer token or X-API-Key header). Endpoints marked Dual-auth also work from the dashboard; API key only endpoints require a key. Game-server plugins poll /rewards/pending and confirm with /rewards/acknowledge; your backend can also subscribe to webhooks for the sale.paid event.
Features
Payments
Create QRPh / e-wallet payment links from the products you set up and follow them from pending to paid. List your catalog and create a payment by product_id to power a fully headless store. All sales are one-time and final — no subscriptions, no surprise reversals.
Rewards delivery
Game-server plugins poll for paid commands and acknowledge them once executed. Per-key server routing means each server only ever receives the rewards meant for it.
Use Rewards deliveryWebhooks
Subscribe to sale.paid and receive a signed POST to your URL the instant an order settles — so your own systems stay in sync without polling. The payload includes the product's commands (with the player name substituted), so a backend of your own can run the rewards itself instead of the plugin. Webhooks are at-least-once — dedupe on order_id.
Wallet & payouts
Read your live balance and a full ledger history of every fee, sale and adjustment — reconciled to the centavo.
Use WalletRefer to the API reference for the full list of endpoints, parameters and responses.
Get started
All endpoints are served from the same origin as your store, under /payments, /rewards and /accounts. To make your first call:
1. Create an API key in your dashboard — each key is bound to one game server.
2. Send the key as a Bearer token (or X-API-Key header).
3. Set up a product in your dashboard, then create a payment with POST /payments/from-product and show the returned QR/link to your buyer — or list your products via GET /products/catalog to build your own store UI.
API reference
Every endpoint below shows its method, path, required credential, parameters and a copyable multi-language sample.
Authentication. Create an API key in your dashboard — each key is bound to one server — and send it on every request as either header:
Authorization: Bearer kp_live_… or X-API-Key: kp_live_…
Each endpoint is tagged with who may call it:
API key only — server-to-server operations (reward delivery, in-game payments, player lookup). Requires an API key; nothing else can call it.
Dual-auth — “read your own data” endpoints (product catalog, payments, wallet) that the dashboard also uses internally. Your API key works exactly the same way — the tag only means the endpoint is shared with the dashboard, so you call it just like the others.
So from your code the rule is simple: always send your API key. Public /pay/{link_id} status endpoints need no auth at all.
variant_id to charge a specific variant; any active sale is applied automatically. Returns a link id, QR and pay URL, with product_id recorded on the order.effective_amount), description, image and variants. Pair with POST /payments/from-product to build a fully headless store.{
"deliveries": [
{
"id": "...",
"link_id": "...",
"command": "/give Notch diamond_sword 1",
"sort_order": 0,
"buyer_name": "Notch",
"created_at": "2026-05-28T21:13:51Z"
}
]
}{
"acknowledged": 2,
"message": "Acknowledged 2 deliveries"
}{ "message": "Payment link cancelled" }; a link that is already paid, expired, or cancelled returns a 400.{
"status": "active",
"expires_at": "2026-05-29T12:00:00+00:00"
}{
"message": "Buyer name saved"
}POST to a URL on your own server — so you never have to poll. Set that URL in your dashboard under Integrations; we sign every request so you can trust it. (Prefer Discord? Paste a Discord webhook URL there instead and we post a channel announcement — no code.)/rewards/pending) on your game server, and for sale alerts you can paste a Discord webhook URL in Integrations — Discord hosts that URL for you, so it's zero code and zero infrastructure.X-KantoPay-Event: sale.paid X-KantoPay-Delivery: <unique id> X-KantoPay-Signature: hex(HMAC-SHA256(secret, raw_body))
X-KantoPay-Signature. Delivery is at-least-once — dedupe on X-KantoPay-Delivery. Respond 2xx to confirm; a non-2xx response is retried with backoff.{
"event": "sale.paid",
"order_id": "...",
"product": "VIP Rank",
"amount": "300.00",
"net": "285.00",
"buyer": "Notch",
"commands": ["lp user Notch parent set vip"],
"paid_at": "2026-06-15T12:00:00Z"
}# You don't call this endpoint — KantoPay sends the request # TO your server. Pick JavaScript, Python, or PHP above for a # receiver that verifies the X-KantoPay-Signature header.// Node.js (Express) const crypto = require('crypto'); const SECRET = process.env.KANTOPAY_WEBHOOK_SECRET; // Use the RAW body for the signature check. app.post('/kantopay-webhook', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.get('X-KantoPay-Signature') || ''; const mac = crypto.createHmac('sha256', SECRET) .update(req.body).digest('hex'); const ok = sig.length === mac.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(mac)); if (!ok) return res.sendStatus(401); const event = JSON.parse(req.body.toString()); // Dedupe on req.get('X-KantoPay-Delivery'), then handle: if (event.event === 'sale.paid') { console.log('Paid', event.amount, 'for', event.buyer); } res.sendStatus(200); });# Python (Flask) import hmac, hashlib, os from flask import Flask, request, abort SECRET = os.environ['KANTOPAY_WEBHOOK_SECRET'].encode() app = Flask(__name__) @app.post('/kantopay-webhook') def webhook(): raw = request.get_data() # exact raw body mac = hmac.new(SECRET, raw, hashlib.sha256).hexdigest() sig = request.headers.get('X-KantoPay-Signature', '') if not hmac.compare_digest(sig, mac): abort(401) event = request.get_json() # Dedupe on request.headers['X-KantoPay-Delivery'], then handle: if event['event'] == 'sale.paid': print('Paid', event['amount'], 'for', event['buyer']) return '', 200<?php // PHP $secret = getenv('KANTOPAY_WEBHOOK_SECRET'); $raw = file_get_contents('php://input'); // exact raw body $sig = $_SERVER['HTTP_X_KANTOPAY_SIGNATURE'] ?? ''; $mac = hash_hmac('sha256', $raw, $secret); if (!hash_equals($mac, $sig)) { http_response_code(401); exit; } $event = json_decode($raw, true); // Dedupe on $_SERVER['HTTP_X_KANTOPAY_DELIVERY'], then handle: if ($event['event'] === 'sale.paid') { error_log("Paid {$event['amount']} for {$event['buyer']}"); } http_response_code(200);
FAQ
Do you support subscriptions or refunds?
No. KantoPay is a one-time-purchase model and all sales are final — this keeps fulfilment simple and fees predictable.
How do I make sure each server only gets its own rewards?
Each API key is bound to a single server. A key only receives deliveries routed to that server (plus any product set to deliver to all servers), so every server in your network polls safely with its own key.
What happens if my plugin polls twice?
Delivery is idempotent. A command stays in the pending pool until acknowledged, and acknowledging is safe to retry.
Changelog
2026-06-12 — Per-server reward routing on API keys.
2026-06-09 — Public REST API and this reference.
Glossary
QRPh — the Philippine national QR standard; one QR pays from any participating bank or e-wallet.
Payment link — a hosted checkout page (and QR) for a single order, tracked from pending to paid.
Reward / command — the in-game action delivered to a server when an order settles.
Idempotent — safe to call more than once; repeats don’t double-charge or double-deliver.
KantoPay Docs