Collect your first payment
Three calls and a webhook. Every snippet below is copyable and written in cURL — switch language and the whole page follows.
Authenticate
One key per project, sent as a bearer token on every call. The webhook secret is separate — it never leaves your server and is used only to verify what we send you. Every response is a JSON envelope: { ok: true, data } or { ok: false, error: { code, message } }.
export HIVEPAY_API_KEY="hp_live_…" # from the portal or your onboarding export HIVEPAY_WEBHOOK_SECRET="whsec_…" # verifies what we send you # every call carries the key as a bearer token curl https://hivepay.asia/api/v1/payment-methods \ -H "Authorization: Bearer $HIVEPAY_API_KEY"
Create a payment
From your server, with your user's id and a package id. You get back what to show the buyer: a QR string, a hosted URL, or bank details — branch on kind. Send an idempotencyKey and a repeated call returns the same payment (200 + Idempotent-Replayed: true) rather than a second charge.
curl -X POST https://hivepay.asia/api/v1/payments \
-H "Authorization: Bearer $HIVEPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalUserId": "user_42",
"provider": "KBZPay",
"packageId": "starter",
"idempotencyKey": "user_42:order-8f3a"
}'{
"ok": true,
"data": {
"reference": "HP-MU8LBMEQ-YAJR4SHE",
"status": "PENDING",
"gateway": "DINGER",
"kind": "QR",
"qrCode": "00020101021226670016…",
"checkoutUrl": null,
"externalUserId": "user_42",
"provider": "KBZPay", "providerName": "KBZ Pay", "method": "QR",
"amountMMK": 5000, "creditAmount": 1100, "packageId": "starter",
"expiresAt": "2026-09-20T11:14:03.000Z"
}
}Show the buyer what's happening
Poll the payment while they pay. The status flips exactly once — PENDING to PAID, FAILED, EXPIRED, CANCELLED or REFUNDED — and a late gateway confirmation still wins.
# safe to poll every 2 seconds while the buyer is on the page curl https://hivepay.asia/api/v1/payments/HP-MU8LBMEQ-YAJR4SHE \ -H "Authorization: Bearer $HIVEPAY_API_KEY"
{
"ok": true,
"data": {
"reference": "HP-MU8LBMEQ-YAJR4SHE",
"status": "PAID",
"amountMMK": 5000,
"creditAmount": 1100,
"gatewayTransactionId": "99204118",
"qrCode": null,
"updatedAt": "2026-09-20T08:14:03.000Z"
}
}Verify the webhook, then credit
Compare the HMAC over the timestamp and the RAW body before you trust anything in it, reject timestamps older than 300 seconds, and dedupe on data.reference — every retry and replay carries the same one. Answer 2xx within 10 seconds and the retries stop.
# H Pay sends:
# POST https://yoursite.com/webhooks
# X-HivePay-Event: payment.paid
# X-HivePay-Delivery: <delivery id>
# X-HivePay-Signature: t=1758355443,v1=9f2c…
#
# recompute over "<t>.<raw body>" with your webhook secret
SIGNED="${T}.${RAW_BODY}"
EXPECTED=$(printf "%s" "$SIGNED" \
| openssl dgst -sha256 -hmac "$HIVEPAY_WEBHOOK_SECRET" \
| awk '{print $2}')
[ "$EXPECTED" = "$V1" ] || exit 1{
"id": "<delivery id>",
"event": "payment.paid",
"createdAt": "2026-09-20T08:14:04.000Z",
"livemode": true,
"data": {
"reference": "HP-MU8LBMEQ-YAJR4SHE",
"externalUserId": "user_42",
"status": "PAID",
"gateway": "DINGER", "provider": "KBZPay", "providerName": "KBZ Pay",
"amountMMK": 5000, "creditAmount": 1100, "creditName": "HCoin", "packageId": "starter",
"gatewayTransactionId": "99204118", "amountMismatch": false,
"metadata": {}, "paidAt": "2026-09-20T08:14:03.000Z"
}
}Give your buyers their own page
Mint a link from behind your own login and show it as “My top-ups”. The buyer sees every payment they made with your project, each one’s receipt, and a plain answer to “was I charged?” — without you building any of it. They never need a password or an SMS code on H Pay, because your app already knows who is signed in.
curl -X POST https://hivepay.asia/api/v1/buyer-sessions \
-H "Authorization: Bearer $HIVEPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalUserId": "user_42",
"displayName": "Ma Thida",
"returnUrl": "https://yoursite.com/wallet"
}'{
"ok": true,
"data": {
"url": "https://hivepay.asia/me/bs_…",
"token": "bs_…",
"externalUserId": "user_42",
"expiresAt": "2026-09-20T09:14:03.000Z",
"createdAt": "2026-09-20T08:44:03.000Z"
}
}Put the payment inside your own page
One tag, no framework, about 9 KB. <hpay-qr> draws the code and watches it settle, <hpay-button> opens the picker in a modal, <hpay-checkout> puts it inline. They render in a shadow root, so your CSS can’t reach in and ours can’t leak out. React wrappers are in packages/hpay-react. Style them with theme, accent, radius and locale — the configurator in Portal → Developers → Widgets generates the exact snippet.
<!-- Your page. The session comes from your server, below. -->
<script type="module" src="https://hivepay.asia/v1/widget.js"></script>
<hpay-qr session="cs_…"
theme="dark" accent="#5BE9D7" radius="12"></hpay-qr>
<!-- or a button that opens the whole picker in a modal -->
<hpay-button session="cs_…" label="Top up"></hpay-button>
<!-- or the picker, inline -->
<hpay-checkout session="cs_…" packages="starter,plus,pro"></hpay-checkout>
<script>
// For your interface only. Credit the user from the signed webhook.
document.addEventListener("hpay:paid", (e) => {
console.log("paid", e.detail.reference);
location.reload();
});
</script><!-- What renders, in a shadow root your CSS cannot reach --> the QR on a white plate "Waiting for payment · 4:12" (counting to the real expiry) a tick when the gateway settles it <!-- Events, for your interface only --> hpay:ready hpay:paid hpay:expired hpay:failed hpay:open hpay:close hpay:change hpay:redirect
Or skip the UI entirely
Mint a hosted top-up session instead and send the buyer to H Pay's page. They pick the package and the channel there — on a phone, in-app flows open instead of an unscannable QR — and you get the identical signed webhook back.
curl -X POST https://hivepay.asia/api/v1/checkout-sessions \
-H "Authorization: Bearer $HIVEPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalUserId": "user_42",
"displayName": "Khant",
"returnUrl": "https://yoursite.com/wallet",
"idempotencyKey": "user_42:topup-1"
}'{
"ok": true,
"data": {
"id": "6ab0…",
"url": "https://hivepay.asia/topup/cs_…",
"status": "OPEN",
"externalUserId": "user_42",
"payment": null,
"expiresAt": "2026-09-20T09:14:03.000Z"
}
}Errors you’ll actually hit
Every error is a JSON body with a stable error.code. Match on the code, never on the message. Validation errors add error.details with the fields that failed.
| HTTP | code | What to do |
|---|---|---|
| 401 | UNAUTHORIZED | No bearer token, a wrong one, or a key that has been rotated. Sign in to the portal with the current key. |
| 400 | VALIDATION_ERROR | The body doesn't match the schema; details.fieldErrors names each field. Fix the request, don't retry it. |
| 400 | BAD_REQUEST | A valid shape we can't act on: unknown packageId, a channel that's switched off, missing billing for a card, an amount outside 500 – 999,999,999 MMK. |
| 404 | NOT_FOUND | That reference or session isn't on your project. References are project-scoped. |
| 409 | CONFLICT | The state forbids it: cancelling a payment that isn't PENDING, or one whose slip is under review. |
| 502 | GATEWAY_ERROR | The upstream rail refused to open the payment. Nothing was created — offer the buyer another channel. |
| 503 | SERVICE_UNAVAILABLE | That channel is off for now (globally or for your project) or not configured. Read GET /payment-methods and show only what's live. |
Hand it to an AI agent
The same contract is served as a machine-readable spec from this domain, so a coding agent can fetch it itself — exact contracts, MUST rules and eight acceptance tests — no pasting docs into a prompt.
Test it before you ship it
A test-mode instance runs the whole pipeline against a simulated gateway — create a payment, fire a callback, receive your webhook with livemode: false and an HP-TEST- reference. It can never be mistaken for real money. Bank-slip payments can be exercised end to end, including the review step.
H Paydocs