KantoPay Docs
Log in

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 started

The 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.

Use Payments

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 delivery

Webhooks

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.

Use Webhooks

Wallet & payouts

Read your live balance and a full ledger history of every fee, sale and adjustment — reconciled to the centavo.

Use Wallet

Refer 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.

POST/payments/from-product
Recommended. Create a payment from a product you set up in your dashboard. The name, price, description, delivery commands and target servers all come from the product — the caller can’t set the price. Pass 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.
API key only
Parameters
api_key required
product_id required
variant_id optional
buyer_name optional
recipient_name optional
buyer_email optional
metadata optional JSON
Response
GET/products/catalog
List your active products so a plugin, in-game menu or custom storefront can show what’s for sale. Each entry has the product’s id, name, price (and sale-adjusted effective_amount), description, image and variants. Pair with POST /payments/from-product to build a fully headless store.
Dual-auth
Parameters
api_key required
Response
GET/payments/list
List all payments with pagination.
Dual-auth
Parameters
api_key required
page optional
limit optional
Response
GET/rewards/pending
Game-server plugins poll this endpoint to receive pending command deliveries for completed payments. Returns commands that have not yet been acknowledged. Each API key belongs to one server — a key only receives the rewards routed to that server (plus any product set to deliver to all servers), so every server in your network can safely poll with its own key.
API key only
Parameters
api_key required
Response
{
  "deliveries": [
    {
      "id": "...",
      "link_id": "...",
      "command": "/give Notch diamond_sword 1",
      "sort_order": 0,
      "buyer_name": "Notch",
      "created_at": "2026-05-28T21:13:51Z"
    }
  ]
}
POST/rewards/acknowledge
Game-server plugins call this endpoint after executing commands to confirm delivery. Acknowledged commands are removed from the pending pool. A key can only acknowledge the deliveries routed to its own server.
API key only
Parameters
api_key required
delivery_ids required
Response
{
  "acknowledged": 2,
  "message": "Acknowledged 2 deliveries"
}
POST/payments/list/{id}/cancel
Cancel an active (unpaid) payment link so the buyer can no longer pay it. Returns { "message": "Payment link cancelled" }; a link that is already paid, expired, or cancelled returns a 400.
Dual-auth
Parameters
api_key required
id required
Response
GET/pay/{link_id}/status
Public status check for a payment link. Used by the buyer payment page to poll for payment completion. No authentication required.
Public — no auth required
Parameters
link_id required
Response
{
  "status": "active",
  "expires_at": "2026-05-29T12:00:00+00:00"
}
POST/pay/{link_id}/claim
Buyer sets their player name on a payment link before paying. Required for products that have commands (ranks, kits, etc.). No authentication required.
Public — no auth required
Parameters
link_id required
buyer_name required
Response
{
  "message": "Buyer name saved"
}
GET/accounts/wallet
Get current wallet balance.
Dual-auth
Parameters
api_key required
Response
GET/accounts/wallet/history
Get wallet transaction history.
Dual-auth
Parameters
api_key required
Response
POSTsale.paid → your webhook URL
This is the one thing here you don't call — KantoPay calls you. Every other endpoint above is a request your code sends to us. A webhook is the reverse: the moment a player's payment clears, KantoPay sends an HTTP 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.)
Player pays KantoPay POSTs the sale to your URL your server verifies the signature & reacts
A custom URL like this needs your own server, so it's for sellers who already run a backend (a stats site, CRM, their own database). No website of your own? You don't need it. In-game rewards arrive via the plugin (/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.
Headers & verification
X-KantoPay-Event:     sale.paid
X-KantoPay-Delivery:  <unique id>
X-KantoPay-Signature: hex(HMAC-SHA256(secret, raw_body))
Compute HMAC-SHA256 of the exact raw request body with your signing secret (shown in Integrations) and compare it, in constant time, to 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.
Body
{
  "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"
}
Receive & verify it on your server
# 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 · Payments API · Rust · PostgreSQL