Wallet
Portal
Identity & Session

Portal — Identity & Session

Who calls these endpoints?Portal / SDK
Base path
/api/portal/v1/wallet/auth
Identity
register, login, otp/verify, otp/resend, password/reset* are opened by the publishable key (rate limited per IP). Past that point session, sessions, devices, logout require a customer session.
Authorization
The session itself is the authorization; a user reaches only their own record.Step-up: for money-out actions the session alone is not enough; an OTP code or a trusted action token is required.
Cross-surface
This surface is for the end user; your merchant server does not call it. For the server-side counterpart see the s2s API.

An end-user session is established in three stages: identification (publishable key) → identity (register/login) → verification (OTP). Sensitive operations add step-up on top.

All endpoints live under /api/portal/v1/wallet/auth/*.

Registration

POST /api/portal/v1/wallet/auth/register
FieldTypeRequiredDescription
phonestring✅Phone number — the wallet's deduplication key
passwordstring✅
emailstring—
programIdstring—When you run several programs
consentCodesstring[]—Agreement codes the user accepted
deviceobject—Device descriptor (below)
threatPayloadobject—Device risk signals

Device object:

FieldDescription
fingerprintHashDevice fingerprint
platformOS/platform
installIdInstall identifier
deviceNameName shown to the user
⚠️

The phone number is the wallet's identity. Binding a wallet to an unverified number means handing it to whoever takes over that number — which is why registration does not complete without OTP verification.

Login

POST /api/portal/v1/wallet/auth/login

phone + password (plus optional programId, device, threatPayload). The response returns an OTP challenge (challengeId).

OTP verification

POST /api/portal/v1/wallet/auth/otp/verify     # { challengeId, code, programId?, device? }
POST /api/portal/v1/wallet/auth/otp/resend     # { challengeId, programId? }

On success the session opens: access + refresh tokens.

Expose otp/resend behind a countdown. Apps that restart the whole login instead of resending put users into an unnecessary loop.

Session management

POST /api/portal/v1/wallet/auth/refresh            # { refreshToken? }
POST /api/portal/v1/wallet/auth/logout
GET  /api/portal/v1/wallet/auth/session            # current session
GET  /api/portal/v1/wallet/auth/sessions           # all active sessions
DELETE /api/portal/v1/wallet/auth/sessions/{sessionId}

GET /api/portal/v1/wallet/auth/sessions and DELETE /api/portal/v1/wallet/auth/sessions/{id} power the user's "sign out other devices" screen. Put it in settings — in wallet apps it is an expected capability.

Device management

GET    /api/portal/v1/wallet/auth/devices
PATCH  /api/portal/v1/wallet/auth/devices/{deviceId}    # { deviceName }
DELETE /api/portal/v1/wallet/auth/devices/{deviceId}

Users can name and remove devices. Removing a device ends its session.

Password operations

POST /api/portal/v1/wallet/auth/password/change            # { currentPassword, newPassword }
POST /api/portal/v1/wallet/auth/password/reset             # { phone, programId? } → challengeId
POST /api/portal/v1/wallet/auth/password/reset/complete    # { challengeId, code, newPassword, programId? }

Password reset is OTP-verified as well.

Step-up verification

Having a session is not enough to move money out. Transfers, withdrawals, QR payments and bill payments require additional verification:

MethodWhen
otpCodeThe code sent when the operation was initialized
trustedActionTokenA previously obtained trusted-action token
⚠️

Step-up tokens are single-use. Do not reuse one on a second operation; every money movement demands its own verification.

Rate limiting

Identity endpoints are rate limited — brute force and OTP flooding are blocked. In your app:

  • Show a wait time after consecutive failures.
  • Gate the resend button behind a countdown.
  • Never build an automatic retry loop.

Flow summary

Identify the app

Send requests with the publishable key.

Register or log the user in

register / login → challengeId.

Verify the OTP

otp/verify → session tokens.

Call /me/*

With the access token; only the user's own data.

Step up on outbound money

otpCode or trustedActionToken.

Common mistakes

SymptomCauseFix
Registration never completesOTP not verifiedFinish the flow with otp/verify
Sessions drop earlier than expectedNo refresh flowUse auth/refresh
Money operations return 4xxMissing step-upSend otpCode / trustedActionToken
You keep hitting rate limitsAutomatic retriesRemove the loop, show the user a countdown