Top-up — money in from outside
A top-up moves real money from a card or bank into a wallet, through a payment gateway. It is the one flow with a genuinely asynchronous middle, and the most common integration mistake is treating the redirect as the answer.
Server-initiated
POST /wallets/{walletId}/top-up-link
{ "amount": 50.00, "returnUrl": "https://yourtenant-app.wallet.gohubpay.com.my/topup/done" }
Returns a hosted payment URL. Open it in the system browser or a webview; the user pays there.
It takes no Idempotency-Key. A retry is not a replay — it mints a second live payment link.
Store the URL you got back and reuse it rather than calling again.
returnUrl must be an https URL on a platform host. A custom scheme like yourapp:// is not
accepted, and it does not error — an unacceptable value is silently replaced with the default
return page, so your app never receives the user back. For a native app, return to an https page you
control the deep-linking from.
User-present
If your app already holds a wallet-user token, it can start its own top-up with
POST /me/top-ups — see the Client app guide. Same settlement path.
Getting the outcome
The user coming back to your returnUrl means the browser navigated. It does not mean the payment
settled, and a redirect can be forged. Never credit anything off the redirect.
Today, poll.
GET /wallets/{walletId}/top-ups
Status moves CREATED → PENDING → SUCCEEDED → CREDITED (or FAILED / EXPIRED / CANCELLED).
Poll no tighter than a few seconds, and give up gracefully — design a real waiting state, because
most settle in seconds and some do not.
CREDITED, not SUCCEEDEDSUCCEEDED means the payment cleared at the gateway. The wallet is credited on the next
transition, and only CREDITED means the money is in the balance.
A poller that stops at SUCCEEDED shows the user a balance that has not moved yet — and because
CREDITED is where it settles, it never sees the state it was actually waiting for.
We do not call your endpoint when a top-up settles. Signed outbound webhooks with retries and a delivery log are designed and not yet built, so polling is the supported mechanism, not a fallback.
The webhook endpoints you may see referenced elsewhere are ones the payment gateway calls on us. They are not part of your integration and you cannot call them.
What is true afterwards
GET /wallets/{id}/top-ups shows CREDITED; the wallet balance has increased by the amount; and a
balanced ledger pair exists for it. If the balance moved and the top-up still reads PENDING, that
is a discrepancy worth reporting.
Next: Money out.