Skip to main content
Use POST /v1/otp/verify to check a plaintext code submitted by the end user. The endpoint is keyed on (phone_number, developer app, code) — the developer app is resolved from your bound X-API-Key, so you do not send app_key when the key is bound. There is no separate otp_id to track, and the call is the same regardless of whether the code was originally delivered via SMS, voice call, or WhatsApp. The server looks up the single active OTP for that phone within your Developer App and compares the bcrypt hash. The API enforces a hard cap of 3 attempts per active OTP. After the third wrong code the OTP is auto-locked and the user must request a new one. Drive your UI off data.remaining_attempts so the user always knows where they stand.

Endpoint

Headers: X-API-Key (required — bind to a Developer App for OTP), Content-Type: application/json (required).

Request body

Trim whitespace and strip non-digits from the user’s input before submitting.

Success response

The OTP is marked used; subsequent verifies for the same code return success: false. If you passed callback_url on the original request or resend, Karibu also sends an async flake.verified webhook to that URL (best-effort). On max-attempt lockout, a flake.failed callback is sent instead. The synchronous response above is unchanged — see Flake lifecycle callbacks.

Handled errors

These are returned in the standard envelope with success: false. Branch on body.message and body.data.remaining_attempts: Auth (401), host/scoping (403), and validation (422) errors share the same shape as on /v1/otp/request. See Error scenarios for the full matrix.

Code samples

Best practices

  • Branch on success first, then on data.remaining_attempts. Treat 0 as a hard lockout — the user must resend.
  • Never log the plaintext code. Don’t echo it from your verify form, store it in analytics, or include it in error reports.
  • Strip whitespace and non-digits from the user’s input before submitting to avoid spurious “Invalid OTP code” responses.
  • Use the same bound API key (and Developer App) that issued the OTP. Cross-app verifies always fail with "No valid OTP found".

What’s next