Skip to main content
CBPay can require a one-time verification code (OTP) before sensitive actions: logging in, creating a payout, withdrawing crypto, revealing a card, issuing an API key… The code arrives over SMS or WhatsApp to the account’s phone, and your operator decides which actions require it and over which channel — per account or for the whole organization.
OTP applies only to user sessions (JWT login). pk_ API keys are exempt: they are server-to-server integrations with no human holding a phone on the other side. If your whole operation runs on API keys, this page changes nothing in your integration.

How it works

The otp_token is single use, bound to your user and to the action it was issued for, and expires with the challenge (10 minutes after the code was sent).

1. Check your policy

GET /v1/otp/settings tells you whether OTP is active and which actions require it:

2. Request the code

Response 201 — the code is already on its way to the account’s phone:
If the account has no phone: 409 phone_required (set it with PATCH /v1/me). Hourly send limits apply — exceeding them returns 429 too_many_attempts.

3. Verify the code

Wrong code → 401 invalid_code (you get 5 attempts per challenge).

4. Execute the action with the token

The token is consumed on use, even if the action fails afterwards (for example, insufficient funds): retrying requires verifying a new challenge. Your idempotency_key remains the duplicate protection — OTP does not replace it.

Two-step login

If your policy requires OTP on login, POST /v1/auth/login no longer returns the session directly:
The pending_token cannot call the API: it is only exchanged, along with the code, for the real session:

The account’s phone

  • E.164 format (+56912345678), set at registration, through PATCH /v1/me or by your operator.
  • phone_verified flips to true the first time you verify a challenge: it proves the holder has the phone in hand.
  • Changing the phone (action phone_change) validates the code against the previous number — nobody can redirect your codes without holding your current phone.
  • If the phone is linked for the first time (or changed without verification), SMS/WhatsApp challenges stay locked for 24 hours: the anti session-hijacking window. During the cooldown, if you have the authenticator app enrolled or a verified email, the challenge is issued automatically over that stronger factor (the channel comes back in the challenge response); you only get 403 phone_binding_cooldown when no alternative factor exists. A phone set by your operator has no cooldown.
  • The two-step login honors the same rule: with login 2FA over SMS/WhatsApp and the phone in cooldown, the login code is issued over the authenticator app or your login email instead (the effective channel comes back in the login response) — it is never sent to a recently linked, unverified number. Without an alternative factor the login responds 403 phone_binding_cooldown until the cooldown expires.

Errors

FAQ

No. pk_ API keys are exempt by design: automation never goes through OTP. Guard your keys accordingly — issuing a new key CAN require OTP (action api_key_create).
The channel is configured by your operator per action (per account or for the whole organization). You see it in GET /v1/otp/settings.
The code and challenge last 10 minutes. You get 5 verifications per challenge and an hourly send limit. The resulting otp_token is single use.
No: the token is bound to the exact action the challenge was created for (a payout token does not work for transfer) and is consumed on first use.
No. A consumed token only means you must verify a new challenge; the operation’s idempotency_key still guarantees there are no duplicates.
Last modified on July 15, 2026