X-API-Key.
- API surface:
https://karibu.briq.tz/v1/otp/* - Audience: backend engineers integrating phone-based verification (signup, login, transaction confirmation).
- Version:
v1.2
1. Overview
The Karibu OTP API is a stateless HTTP service that:- Generates a numeric OTP (default 6 digits) and delivers it to a phone number via SMS (default), voice call, or WhatsApp.
- Verifies a code submitted by the end user, with a hard cap of 3 attempts per active OTP.
- Manages lifecycle: resend, invalidate, and inspect the currently active OTP for a
(phone, app)pair.
Channel comparison
2. Prerequisites
Before integrating, make sure you have:- A Briq account with a workspace.
- An active developer API key (
X-API-Key) bound to a Developer App. Generate one in the Briq dashboard and attach it to the app that scopes your OTP traffic. - At least one Developer App registered against your workspace (required for binding). Each app gives you an
app_id(UUID) returned in API responses for reference. - The workspace must allow developer access (toggle in workspace settings).
- Recipient phone numbers in E.164 digits-only format (no
+, no spaces, e.g.255712345678).
Bind every OTP API key to a Developer App and keep it server-side. If you don’t yet have a Developer App, see Developer Apps before continuing.Legacy integrations with an unbound API key may scope requests via the optional
X-App-ID header or an app_key field in the body or query. New integrations should bind the key instead.3. Authentication & host
Base URL
/v1/otp/.
Required headers
App scoping
Karibu resolves your Developer App from the boundX-API-Key on every OTP endpoint:
- Your API key must be bound to a Developer App you own.
- The Developer App’s workspace must allow developer access for your user — otherwise 403 (
Workspace does not allow developer access.). - If you send optional
X-App-ID, it must match the app bound to your API key — otherwise 403.
4. Standard response envelope
Every endpoint returns the same JSON envelope:success—truefor normal operation,falsefor handled errors / “not found” cases embedded in the envelope.message— short human-readable string suitable for logging.data— endpoint-specific. May benull.status_code— mirrors the HTTP status code returned.
5. Behavioral rules (read this first)
These rules apply across all endpoints. Designing your client around them upfront prevents most integration bugs.6. Endpoint reference
Each endpoint section below documents purpose, contract, error semantics, and copy-pasteable client snippets in cURL, Python (requests), Node.js (fetch), and PHP (cURL extension).
6.1 POST /v1/otp/request
Generate a fresh OTP and deliver it to the phone.
Method & path: POST https://karibu.briq.tz/v1/otp/request
Headers: X-API-Key (bound to a Developer App), Content-Type: application/json.
Common fields (every channel):
The remaining fields are channel-specific. Pick the tab below that matches the channel you’re sending on — each one shows the exact payload, the success response shape, and copy-pasteable client snippets.
Success response (same shape across channels):
Behavior: invalidates the prior active OTP for
(phone, app) before issuing the new one. Returns 200 only after the dispatch is accepted by the upstream provider.
Channel-specific payload & code samples
- SMS
- Voice call
- WhatsApp
Default channel. Customisable per call via
sender_id and message_template.Channel-specific fields:Exact payload:
6.2 POST /v1/otp/verify
Check a code submitted by the end user.
Method & path: POST https://karibu.briq.tz/v1/otp/verify
Headers: X-API-Key (bound to a Developer App), Content-Type: application/json.
Request body:
Success 200:
success: false.
When a callback URL was set on request/resend or passed on verify, Karibu also sends an async flake.verified or flake.failed webhook (best-effort). See §10 Per-request flake callbacks.
Handled errors (envelope, success: false):
Auth/host errors (
401, 403) and validation (422) are the same as on /request.
Code samples
6.3 POST /v1/otp/resend
Issue a new OTP after invalidating the previous one. Body shape and channel-specific fields are identical to §6.1 /request; only the path changes.
Method & path: POST https://karibu.briq.tz/v1/otp/resend
Success 200:
/request.
request vs resend — what’s the difference? Functionally both invalidate any prior active OTP and dispatch a new one. Use /resend when the end user explicitly clicks “Resend code”; this lets you treat it differently in your analytics, rate-limits, or UI without changing payloads.Switching channels on resend is allowed — e.g. user clicks “I didn’t get the SMS, send via WhatsApp instead.” Just send the new
delivery_method. The previous active OTP is invalidated regardless of which channel issued it.Channel-specific payload & code samples
- SMS
- Voice call
- WhatsApp
cURL
Python
Node.js
PHP
6.4 POST /v1/otp/invalidate
Force-expire any active OTP for a phone scoped to your Developer App. Useful on logout, security events, or when the user changes their phone number.
Method & path: POST https://karibu.briq.tz/v1/otp/invalidate
Request body:
Success 200:
Idempotent. Calling this when there is no active OTP also returns 200 — it simply has nothing to invalidate.
Code samples
6.5 GET /v1/otp/status
Inspect the currently active OTP for a phone, scoped to your Developer App, without triggering a send. Useful for UI countdowns, diagnostics, and idempotent UX (e.g. “an OTP was already sent — please check your phone”).
Method & path: GET https://karibu.briq.tz/v1/otp/status
Query parameters:
Success 200:
Code samples
7. End-to-end flow
A typical phone-verification flow:- User enters phone in your UI → your backend calls
POST /v1/otp/requestwith the appropriatedelivery_method. - User receives SMS, voice call, or WhatsApp message with the plaintext code.
- User submits code in your UI → your backend calls
POST /v1/otp/verify.- On
success: true→ mark phone verified, continue signup/login. - On
success: false→ show error and usedata.remaining_attemptsto drive UX.
- On
- User clicks “Resend” → your backend calls
POST /v1/otp/resend(which invalidates the prior code automatically). You may switch channels here — e.g. retry on WhatsApp after an SMS didn’t arrive. - User logs out / changes phone → your backend calls
POST /v1/otp/invalidate. - Optional UI affordances: poll
GET /v1/otp/statusto drive countdown timers or detect that an OTP is already in-flight before issuing a new one.
8. Error scenarios — quick reference
9. Best practices
- Bind your API key to a Developer App. Karibu resolves the app from
X-API-Key— do not sendapp_keyin request bodies or query params in new integrations. - Never expose your API key to untrusted clients. Keep it server-side; have your frontend hit your backend, which then calls Karibu with
X-API-Keyonly. - Never log the plaintext code. Treat it as a credential. Don’t echo it from your verify form, don’t store it in analytics, don’t include it in error reports.
- Respect the 3-attempt cap in your UI. Disable the verify button after 3 failures and surface “Resend code” prominently.
- Handle
remaining_attempts: 0distinctly fromremaining_attempts > 0. They demand different UX (lockout vs retry). - Invalidate on logout. It’s safe to call even if you’re unsure an OTP is active.
- Default to
delivery_method: "sms". Use"call"for SMS-restricted regions or accessibility; use"whatsapp"for app-installed users with chat-first habits. - Cascade channels for reliability. A common pattern: try
"whatsapp"first; if the dispatcher returns a 400 (template not approved, dispatcher misconfigured) fall back to"sms", and offer"call"as a manual third option. - Sender ID is SMS-only. A misconfigured
sender_idmay silently route through the platform default; verify deliverability with a test number before launch. The field is ignored on"call"and"whatsapp". - WhatsApp template wording is fixed. Don’t ship a UI that promises a custom message body when sending over WhatsApp — only
briq_otpis dispatched, so the recipient sees the platform-approved Meta template. - Rate-limit your own users. The API does not enforce per-phone request rate limits — your application should.
10. Per-request flake callbacks
OTP codes are internally called flakes. Optionalcallback_url and callback_secret on request, resend, and verify let you receive async HTTPS webhooks on verify outcomes — without registering a dashboard webhook.
For a narrative walkthrough and step-by-step testing with webhook.site, see Flake lifecycle callbacks.
Fields
Events
Callbacks use
channel: "flake" and X-Briq-Service-Type: flake. The plaintext code is never included.
Envelope example (flake.verified)
/verify responses are unchanged if a callback fails.
Testing in the API playground
- Open POST /v1/otp/request in this reference’s interactive playground.
- Set
callback_urlto a public HTTPS inspector URL (e.g. from webhook.site). - Optionally set
callback_secretand note it for signature checks. - Call POST /v1/otp/verify with the code received on the phone.
- Confirm the inspector receives
flake.verifiedwithin a few seconds.
callback_url fields on POST /v1/otp/resend to set the callback target for a resent code.
11. Changelog
- v1.2 — optional per-request
callback_urlandcallback_secretonrequest,resend, andverify; asyncflake.verifiedandflake.failedcallbacks (best-effort, signed when secret provided). - v1.1 — added WhatsApp delivery channel (
delivery_method: "whatsapp"). Routes through the platform-managedbriq_otptemplate via the WhatsApp dispatcher; sender resolution is automatic (developer-owned sender → env-pinned platform default →is_default = TRUEfallback).sender_idandmessage_templateare ignored on this channel./requestand/resendpayload tables split into channel-specific tabs. - v1 — initial public surface:
request,verify,resend,invalidate,status. SMS + voice call delivery. 3-attempt cap. Single-active-OTP per(phone, app).