Briq OTP codes are internally called flakes. In addition to the synchronous /v1/otp/verify response, you can register an optional per-request callback URL to receive async notifications when verification succeeds or fails.
This is separate from registered app webhooks (SMS delivery events). Flake callbacks are one-off: you pass callback_url on a specific request, resend, or verify call — no webhook registration in the dashboard is required.
When to use callbacks
| Use callbacks when… | Rely on /verify only when… |
|---|
| Your backend needs a second system notified after verify (CRM, audit log, workflow engine) | A single synchronous response is enough for your auth gate |
| You want failure signals (max attempts) pushed to your server | You already handle failures from the verify envelope |
| You integrate with event-driven architecture | You poll or trust client-side state only |
Callbacks are best-effort. Always use the synchronous /v1/otp/verify response as the source of truth for granting access. Treat callbacks as notifications, not as the primary auth decision.
How it works
- Request or resend — pass optional
callback_url and callback_secret. Karibu associates them with this OTP for its lifetime (minutes_to_expire, default 10 minutes), scoped to your Developer App and phone number.
- Verify — you get the normal synchronous
/verify response first. If a callback URL was set on request/resend (or passed again on verify), Karibu also sends an async webhook to that URL shortly after.
- Your endpoint — receives a JSON
flake.verified or flake.failed envelope. Respond with 2xx; Karibu retries on failure.
Calling /v1/otp/invalidate, or issuing a new /request or /resend, replaces the callback association for that (phone, app) pair.
Request fields
Available on POST /v1/otp/request, POST /v1/otp/resend, and POST /v1/otp/verify:
| Field | Type | Required | Notes |
|---|
callback_url | string | no | Must be HTTPS. Must not resolve to private, loopback, or link-local addresses. Rejected with 422 if invalid. |
callback_secret | string | no | When set, callbacks include X-Briq-Signature: sha256=<hex> (HMAC-SHA256 over the raw JSON body). |
On verify, you may pass callback_url without callback_secret — Karibu reuses the secret from the original request/resend when available.
Events
| Event | When (Karibu OTP API) |
|---|
flake.verified | /v1/otp/verify returns success: true |
flake.failed | /v1/otp/verify hits max attempts (reason: "max_attempts") |
The plaintext OTP code is never included in callback payloads.
flake.verified payload
{
"id": "evt_a1b2c3d4e5f6...",
"event": "flake.verified",
"channel": "flake",
"app_id": "550e8400-e29b-41d4-a716-446655440000",
"created_at": "2026-06-07T12:00:00.000Z",
"data": {
"flake_id": "otp-uuid-here",
"phone_number": "255712345678",
"status": "verified",
"verified_at": "2026-06-07T12:00:05.000000"
}
}
flake.failed payload (max attempts)
{
"id": "evt_f6e5d4c3b2a1...",
"event": "flake.failed",
"channel": "flake",
"app_id": "550e8400-e29b-41d4-a716-446655440000",
"created_at": "2026-06-07T12:02:00.000Z",
"data": {
"flake_id": "otp-uuid-here",
"phone_number": "255712345678",
"status": "failed",
"reason": "max_attempts",
"failed_at": "2026-06-07T12:02:00.000000",
"remaining_attempts": 0
}
}
Outbound request shape
| Aspect | Value |
|---|
| Method | POST |
Content-Type | application/json |
User-Agent | Briq-Webhook/1.0 |
X-Briq-App-ID | Your Developer App UUID |
X-Briq-Service-Type | flake |
X-Briq-Signature | Present when callback_secret was provided |
Signature verification matches registered webhooks: HMAC-SHA256 over the raw request body bytes.
Testing callbacks end-to-end
Use a public HTTPS request inspector (e.g. webhook.site) or your own staging endpoint.
Step 1 — capture a callback URL
- Open webhook.site (or similar) and copy your unique HTTPS URL.
- Optionally choose a test secret, e.g.
test-callback-secret-123.
Step 2 — request an OTP with callback
curl -X POST https://karibu.briq.tz/v1/otp/request \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "255712345678",
"app_key": "YOUR_APP_KEY",
"delivery_method": "sms",
"callback_url": "https://webhook.site/YOUR-UNIQUE-ID",
"callback_secret": "test-callback-secret-123"
}'
The synchronous response is unchanged — you still only get expires_at, not the code.
Step 3 — verify the code
Submit the code the recipient received:
curl -X POST https://karibu.briq.tz/v1/otp/verify \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "255712345678",
"app_key": "YOUR_APP_KEY",
"code": "123456"
}'
Within a few seconds, your inspector should receive a POST with event: "flake.verified".
Step 4 — verify the signature (optional)
If you set callback_secret, confirm X-Briq-Signature matches HMAC-SHA256 of the raw body. See Webhooks → Verifying signatures.
Step 5 — test failure callback
Request a new OTP (same callback_url), then call /verify three times with wrong codes. On the third failure you should receive flake.failed with reason: "max_attempts".
API playground
The Karibu OTP API reference documents callback_url and callback_secret on request, verify, and resend. Use the interactive playground on those endpoints with your API key and a webhook.site URL.
422 on bad URLs. http:// URLs, localhost, and hostnames that resolve to private IPs are rejected at request validation time with a 422 body error — before any OTP is sent.
Best practices
- Return 2xx quickly from your callback handler. Briq retries on non-2xx responses with exponential backoff.
- Make handlers idempotent — use
data.flake_id or envelope id to deduplicate.
- Do not block login on callbacks — proceed from the synchronous verify response; process the webhook asynchronously.
- Rotate
callback_secret per environment (staging vs production) and store it server-side only.
- Use resend to update the callback — a new
callback_url on /resend replaces the callback target for the new OTP window.
What’s next