Skip to main content

What your app calls

Needs a delegated session

This page is about the part of your integration that runs in your app, using a wallet-holder token. You get one by minting a delegated token for the user.

If your backend instructs every movement on its own credential instead, your users hold no session and the operations marked User token are not part of your build.

This is the part of your integration that runs in your app, carrying the token of the person holding the wallet rather than your machine token. For the server-to-server half, see Start here.

What makes it different

Every /me/* operation resolves the caller from the token and acts only as that person. There is no userId parameter to get wrong, and no way to ask for someone else's data. That is what makes it safe to ship inside a mobile app.

The practical consequences:

  • A machine token cannot call these. It has no wallet holder to resolve to, and is rejected.
  • Withdrawals live here, not on the tenant surface. The person withdrawing must be the person asking.
  • GET /transactions returns only their activity. The same URL called with your machine token returns your whole tenant; the narrowing happens on our side.

Authentication

Auth is backend-mediated — your app talks to us, never to the identity provider directly.

POST /auth/loginemail + password → access token
POST /auth/refreshnew access token
POST /auth/logoutend the session
POST /auth/forgot-password · /auth/reset-passwordself-service recovery
GET /auth/password-policyso your UI can validate before submitting
POST /auth/invite/completefirst login after an invite — consumes the emailed token and sets the password

Token storage. Keep the access token in memory. The refresh token differs by platform: on web it is an HttpOnly cookie you never see; on native there are no cookies, so send X-Client: mobile and you receive it in the response body — store it in the platform keychain (flutter_secure_storage, Keychain, Keystore). Never localStorage.

Resolving the tenant

Before login, the app needs to know which tenant it belongs to and what to look like:

GET /public/tenants/resolve

Resolved from the host, so a white-label build serves every tenant from one binary. Returns display name, logo and brand colours.

It is not the only call that needs no token — so do GET /auth/password-policy, POST /auth/login, POST /auth/forgot-password, POST /auth/reset-password and POST /auth/invite/complete, for the obvious reason that a person who cannot sign in has nothing to present.

What your app can do

Area
WalletsGET /wallets, balances, fund buckets
CapabilitiesGET /me/wallet-types — which actions to show
ActivityGET /transactions, /{id}, /{id}/detail — their own
Send moneyPOST /transfers/peer, GET /transfers/lookup-recipient
Top upPOST /me/top-ups, GET /me/top-ups
Withdraw/me/withdrawal-accounts, /me/withdrawals
NotificationsGET /me/notifications, unread count, mark read
BanksGET /banks — for the payout account form

Two things that will bite you

Withdrawals require KYC VERIFIED. Adding a payout account and requesting a payout both return 403 KYC verification required until a GoHubPay reviewer has verified the user. Neither your app nor your backend can grant that. Build a real "verification pending" state — it is a normal condition, not an error.

Never credit a top-up off the return URL. The user landing back in your app means the browser navigated, not that money moved. Poll GET /me/top-ups/{id} until it reads SUCCEEDED.

The Screens → API guide maps each of these to a real screen.