Integration

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.

BASE URLhttps://hivepay.asia/api/v1 — HTTPS in production, JSON in and out.
AUTHAuthorization: Bearer hp_live_… on every call. Webhooks are signed with whsec_….
EVENTSpayment.paid · payment.failed · payment.proof_submitted. Subscribe per endpoint; most integrations only need payment.paid.
RETRIES8 attempts, 1 min → 24 h. Replayable from the portal afterwards. A failing endpoint is paused, never dropped.
EXPIRYA pending payment expires after 180 min; a hosted session after 60 min by default (expiresInMinutes 5 – 1440).
BALANCESYours. H Pay confirms money arrived; your code grants the credit in your database.
0

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

iKeys are project-scoped. Test mode marks every payload livemode: false and every reference HP-TEST-, so a test event can never be mistaken for real money.
bash
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"
1

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.

iCard channels (MPU, Visa, Mastercard, JCB, MAB Bank) also need billing: { email, billAddress, billCity, state, country, postalCode } — the hosted page collects it for you.
bash
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"
  }'
Response201 Created
{
  "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"
  }
}
2

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.

!Never credit a user from a polled status. Polling is for the buyer's eyes; the webhook is the fact you act on.
bash
# 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"
Response200 OK
{
  "ok": true,
  "data": {
    "reference": "HP-MU8LBMEQ-YAJR4SHE",
    "status": "PAID",
    "amountMMK": 5000,
    "creditAmount": 1100,
    "gatewayTransactionId": "99204118",
    "qrCode": null,
    "updatedAt": "2026-09-20T08:14:03.000Z"
  }
}
3

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.

!Parse the body AFTER verifying. Frameworks that auto-parse JSON change the bytes and break the signature — take the raw buffer.
verify.sh
# 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
Responsethe body we POST
{
  "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"
  }
}
5

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.

!The URL is the capability: anyone holding it sees that buyer’s payments with your project. Mint it on the click, keep the expiry short, and never put it in an email you also send to someone else. Only your server can mint one — a checkout link or a receipt can never be turned into a portal session.
bash
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"
  }'
Response201 Created
{
  "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"
  }
}
6

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.

!Two rules. List your domain under Developers → Widgets first, or every call is refused — the session token sits in your page for anyone to read, so the domain is the fence. And credit the user from the signed webhook, never from hpay:paid: a browser can be faked, the HMAC can’t.
index.html
<!-- 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>
Responsein the page
<!-- 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
4

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.

bash
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"
  }'
Response201 Created
{
  "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.

HTTPcodeWhat to do
401UNAUTHORIZEDNo bearer token, a wrong one, or a key that has been rotated. Sign in to the portal with the current key.
400VALIDATION_ERRORThe body doesn't match the schema; details.fieldErrors names each field. Fix the request, don't retry it.
400BAD_REQUESTA 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.
404NOT_FOUNDThat reference or session isn't on your project. References are project-scoped.
409CONFLICTThe state forbids it: cancelling a payment that isn't PENDING, or one whose slip is under review.
502GATEWAY_ERRORThe upstream rail refused to open the payment. Nothing was created — offer the buyer another channel.
503SERVICE_UNAVAILABLEThat 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.

claude code — prompt
>Integrate H Pay top-ups into this project following https://hivepay.asia/llms.txt — create payments server-side, verify the webhook HMAC over the raw body, and dedupe on data.reference.

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.