MarketHK · Phase 4

Customer TOTP API

Replace expensive SMS OTPs with standard Authenticator apps (Google / Microsoft / 1Password). Enroll a contact once, then challenge and verify with a workspace API key.

Auth

  1. In the workspace: TOTP → API → create an API key (scope totp).
  2. Send Authorization: Bearer mk_live_… (or X-Api-Key).

Endpoints

GET  /v1/totp
GET  /v1/totp?contactId=…
POST /v1/totp/enroll
POST /v1/totp/confirm
POST /v1/totp/challenge
POST /v1/totp/verify
POST /v1/totp/disable

# Same actions also work as:
POST /v1/totp   { "action": "challenge", ... }

Enroll + confirm

curl -s -X POST https://YOUR_HOST/v1/totp/enroll \
  -H "Authorization: Bearer mk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"contactId":"CONTACT_UUID","externalUserId":"bank-user-123","issuer":"YourBank"}'

curl -s -X POST https://YOUR_HOST/v1/totp/confirm \
  -H "Authorization: Bearer mk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"contactId":"CONTACT_UUID","code":"123456"}'

Challenge + verify

curl -s -X POST https://YOUR_HOST/v1/totp/challenge \
  -H "Authorization: Bearer mk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"contactId":"CONTACT_UUID","purpose":"login"}'

curl -s -X POST https://YOUR_HOST/v1/totp/verify \
  -H "Authorization: Bearer mk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"challengeId":"CHALLENGE_UUID","code":"123456"}'

Hybrid policy (admin-owned)

Default OTP policy is customer_totp → sms, configured by a platform admin for each workspace. Workspace users only use Direct send; they do not pick routing policies. Customer apps call /v1/totp/challenge then /v1/totp/verify; SMS runs only if TOTP is not enrolled or verification fails (per admin policy).

SDK sketch

export async function challengeTotp(apiKey, contactId) {
  const res = await fetch("/v1/totp/challenge", {
    method: "POST",
    headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
    body: JSON.stringify({ contactId, purpose: "transfer" }),
  });
  return res.json();
}

export async function verifyTotp(apiKey, challengeId, code) {
  const res = await fetch("/v1/totp/verify", {
    method: "POST",
    headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
    body: JSON.stringify({ challengeId, code }),
  });
  return res.json();
}

Manage TOTP and API keys · Enroll contacts · Book consultation