J Juk Gateway API reference
User guide Dashboard

Juk Gateway API

One API for ABA PayWay and Bakong KHQR. Create a payment, render the returned QR, and observe the terminal result by webhook or polling.

Base URL and credentials

Base URLhttps://aba-api.annoymous.com — routes sit at the origin root
Authenticated routesAuthorization: Bearer sk_live_…
Public checkout routesx-checkout-key: … — server-side only, never in browser code

Keys belong to the account, not to a single store, so one key reaches every store you own. Keys expire after 90 days and are rate limited to 600 requests per minute. The older store-scoped key still works — it resolves to the owning account.

Routes

MethodPathWhat it does
GET/storesList the account's stores
POST/stores/detailRead one store
POST/storesCreate a store and configure its rails
POST/stores/updateUpdate or deactivate a store
POST/paymentCreate a payment and reserve its KHQR
GET/payment?storeId=…&page=1List and page a store's payments
POST/payment/statusPoll a payment by id or by your tranId
POST/payment/detailRead the full durable payment record
GET/public/payment/:idPayer-safe payment object
GET/public/payment/:id/statusCurrent status and clocks
POST/public/payment/:id/retryRe-check status and reopen ABA polling

Your first payment

1 · Register the store you collect into

curl -X POST https://aba-api.annoymous.com/stores \
  -H 'authorization: Bearer sk_live_…' \
  -H 'content-type: application/json' \
  -d '{"name":"Tykea Coffee","merchantStoreId":"branch-2",
       "paywayLink":"https://link.payway.com.kh/aba?id=usd…",
       "khrPaywayLink":"https://link.payway.com.kh/aba?id=khr…",
       "bakongAccountId":"your_name@aclb"}'

paywayLink is required. khrPaywayLink and bakongAccountId enable the other rails. merchantStoreId is your own identifier — registering it means you never have to store our ids.

2 · Create one payment per sale

curl -X POST https://aba-api.annoymous.com/payment \
  -H 'authorization: Bearer sk_live_…' \
  -H 'content-type: application/json' \
  -d '{"merchantStoreId":"branch-2","amount":"12.50",
       "currency":"USD","provider":"BAKONG","tranId":"INV-1042"}'
{
  "id": "8435481a-48a8-4bb2-91d2-bcd1e604fb17",
  "status": "PENDING",
  "amount": "12.50",
  "currency": "USD",
  "tranId": "INV-1042",
  "qrString": "00020101021230…",
  "khqrMd5": "e0f3d55c4ca178f4d2c45e99d4d9d7f8",
  "paywayLink": null,
  "deeplink": null,
  "checkoutUrl": "https://aba-api.annoymous.com/payment/8435481a-…",
  "expireAt": "2026-08-14T03:14:22.418Z"
}

3 · Render the QR

qrString is a plain KHQR string — draw it anywhere: a web checkout, a POS screen, a printed slip. Or send the payer straight to checkoutUrl, which is the hosted checkout page.

Keep the QR visible. deeplink is derived from ABA's checkout client and is undocumented upstream, so it can change without notice. qrString is the source of truth.

4 · Poll, then reconcile

curl -X POST https://aba-api.annoymous.com/payment/status \
  -H 'authorization: Bearer sk_live_…' \
  -H 'content-type: application/json' \
  -d '{"merchantStoreId":"branch-2","tranId":"INV-1042"}'
{
  "paymentId": "8435481a-…",
  "status": "SUCCESS",
  "expireAt": 1786763662418,
  "settledTranId": "1234567890",
  "receipt": "https://…",
  "at": 1786763501992
}

Poll every two or three seconds until status leaves PENDING. This payload is not the payment object: expireAt and at are epoch milliseconds, and the receipt field is named receipt. While pending, settledTranId and receipt are absent, not null.

A later read looks again

The end of the automatic check window is not the end of the payment. On ABA the QR stays payable for 24 hours while the automatic checks only run for the first few minutes, so a status read taken after that window reopens the checks and a payment made later is still discovered. On Bakong the QR really does die at its deadline and the provider is checked one last time before EXPIRED is written; if Bakong cannot answer the payment stays PENDING, because a local clock never manufactures an EXPIRED status.

A deactivated store still owns its payment history: active: false blocks new payments, but GET /payment, POST /payment/status and POST /payment/detail keep serving the payments that store already owns.

Provider and currency rules

ProviderCurrencyStore requirementAmount rule
ABAUSDpaywayLinkat least 0.01
ABAKHRkhrPaywayLinkat least 100
BAKONGUSDbakongAccountIdat least 0.01
BAKONGKHRbakongAccountIdwhole riel, at least 100

amount is a string and must match ^\d+(\.\d{1,2})?$. No conversion occurs.

Safe retries

tranId is unique within a store. Repeating a create with an existing tranId returns the original payment verbatim, without a second upstream call — a timed-out create is always safe to retry. The original response wins even if the retry changes the amount, currency or provider, so treat tranId as the identity of one payment intent.

A repeat returns 200 with the original payment; the first call returns 201.

Webhooks

Set webhookUrl and webhookSecret on the store to receive one POST when a payment reaches SUCCESS or EXPIRED. A PENDING payment produces nothing.

curl -X POST https://aba-api.annoymous.com/stores/update \
  -H 'authorization: Bearer sk_live_…' \
  -H 'content-type: application/json' \
  -d '{"merchantStoreId":"branch-2","changes":{
       "webhookUrl":"https://yourshop.com/hooks/bongluy",
       "webhookSecret":"whsec_…"}}'

X-Signature is sha256= followed by the hex HMAC-SHA256 of the raw request bytes, keyed by the store's secret. Do not hash re-serialised JSON. Return any 2xx within 10 seconds and fulfil afterwards; everything else is retried 10 times over about 17 minutes.

Errors

{ "statusCode": 400, "message": "…", "error": "Bad Request" }

For validation failures message is an array of field complaints — branch on the type before displaying it.

StatusMeaning
400Validation, missing rail configuration, fractional Bakong KHR, or no recognised store changes
401Missing, invalid, expired, revoked or under-scoped API key
403Unrecognised origin on a public route
404Missing or foreign store/payment
409merchantStoreId already in use
429Rate limited
500Payment provider or server failure — retry with the same tranId

Limits

Authenticated API600 requests per minute per API key
Store writes20 per minute per account
Hosted checkout20 requests per second per payment, 200 per second overall

A local timeout is not an EXPIRED status. Do not release goods on a timeout, but do not ask the payer to pay twice either — reconcile until the server reports a terminal state.