> ## Documentation Index
> Fetch the complete documentation index at: https://docs.briq.tz/llms.txt
> Use this file to discover all available pages before exploring further.

# Flake lifecycle callbacks

> Optional per-request HTTPS webhooks when an OTP (flake) is verified or fails — setup, events, signing, and how to test.

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](/guides/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                   |

<Warning>
  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.
</Warning>

## How it works

```mermaid theme={null}
sequenceDiagram
    participant You as Your backend
    participant Karibu as Karibu OTP API
    participant Hook as Your callback URL

    You->>Karibu: POST /v1/otp/request + callback_url
    Karibu-->>You: 200 OTP sent

    You->>Karibu: POST /v1/otp/verify + code
    Karibu-->>You: 200 verified (sync response)
    Karibu-->>Hook: POST flake.verified (async)
```

1. **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 the Developer App bound to your `X-API-Key` and the phone number.
2. **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.
3. **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

```json theme={null}
{
  "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)

```json theme={null}
{
  "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](/guides/webhooks#verifying-signatures): HMAC-SHA256 over the **raw request body bytes**.

## Testing callbacks end-to-end

Use a public HTTPS request inspector (e.g. [webhook.site](https://webhook.site)) or your own staging endpoint.

### Step 1 — capture a callback URL

1. Open webhook.site (or similar) and copy your unique **HTTPS** URL.
2. Optionally choose a test secret, e.g. `test-callback-secret-123`.

### Step 2 — request an OTP with callback

Use an API key bound to your Developer App — the examples below need only `X-API-Key`.

```bash theme={null}
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",
    "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:

```bash theme={null}
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",
    "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](/guides/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](/Karibu-OTP/index) documents `callback_url` and `callback_secret` on request, verify, and resend. Use the interactive playground on those endpoints with a bound API key and a webhook.site URL.

<Note>
  **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.
</Note>

## 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

* [Requesting OTP codes](/guides/otp-requesting-codes) — full request payload reference
* [Validating OTP codes](/guides/otp-validating-codes) — verify flow and attempt handling
* [Karibu OTP API reference](/Karibu-OTP/index) — endpoint contracts and OpenAPI playground
