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

# Webhooks

> Subscriber guide for HTTPS webhook callbacks from Briq for SMS events—request shape, verification, events, and retries.

# Webhook integration (subscriber guide)

This page describes what **your HTTPS endpoint** receives from Briq when SMS webhooks are enabled for your app. It does not assume access to Briq’s internal systems or source code.

## Overview

Briq sends **asynchronous HTTP callbacks** when SMS-related events occur—for example, after a provider accepts a message for routing, or when a delivery report updates final status. Each callback is an **HTTP POST** to the URL you registered, with a **JSON body** (the event envelope).

Callbacks are sent only when:

1. The SMS was sent using credentials linked to **your developer app**, and
2. You have configured a **webhook** for that app and channel (for SMS, the channel is `sms`).

If webhooks are not configured or the message is not tied to your app, **no callback is sent** for that event.

***

## Request shape

| Aspect         | Value                                                                          |
| -------------- | ------------------------------------------------------------------------------ |
| Method         | `POST`                                                                         |
| Body           | JSON (see [JSON envelope (top-level fields)](#json-envelope-top-level-fields)) |
| `Content-Type` | `application/json`                                                             |

### Outbound headers

Briq sends the following headers on each delivery attempt:

| Header                | Always present                               | Purpose                                                                                                        |
| --------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `Content-Type`        | Yes                                          | `application/json`                                                                                             |
| `User-Agent`          | Yes                                          | `Briq-Webhook/1.0`                                                                                             |
| `X-Briq-App-ID`       | Yes                                          | UUID of your Briq developer app                                                                                |
| `X-Briq-Service-Type` | Yes                                          | Channel, e.g. `sms`                                                                                            |
| `X-Briq-Signature`    | Only when a **signing secret** is configured | `sha256=<hex>` — HMAC-SHA256 over the **raw request body** (see [Verifying signatures](#verifying-signatures)) |

If no secret is configured (or the secret is empty), **`X-Briq-Signature` is omitted**.

***

## JSON envelope (top-level fields)

The body is one JSON object with **stable key ordering** and **no extra whitespace** between tokens (compact JSON). Briq signs exactly those bytes.

| Field        | Type   | Description                                                        |
| ------------ | ------ | ------------------------------------------------------------------ |
| `id`         | string | Unique id for this event (Briq uses an `evt_`-prefixed identifier) |
| `event`      | string | Event name, e.g. `sms.sent`, `sms.delivered`                       |
| `channel`    | string | Lowercase channel, e.g. `sms`                                      |
| `app_id`     | string | Same app UUID as in `X-Briq-App-ID`                                |
| `created_at` | string | UTC timestamp, ISO-8601 with milliseconds and `Z`                  |
| `data`       | object | Event-specific fields (see [SMS events](#sms-events))              |

**Signing:** Always verify the HMAC using the **raw HTTP body** as received. Re-serializing parsed JSON may change spacing or key order and will break verification.

***

## Verifying signatures

### Secret

Use **the webhook signing secret** shown to you when you create or rotate the webhook in the Briq developer console (or that your Briq operator provisioned for you). Store it securely (secret manager, environment variable, never in client-side code).

### Algorithm

1. Take the **raw request body** as bytes (use your framework’s raw body buffer, not a pretty-printed string).
2. Read `X-Briq-Signature`. It has the form **`sha256=<lowercase_hex>`**.
3. Compute `HMAC-SHA256(secret_as_utf8_bytes, raw_body_bytes)` and hex-encode the result.
4. Compare your hex string to the value after `sha256=` using a **constant-time** comparison.

Do not verify by parsing JSON and calling `JSON.stringify` / `json.dumps` unless you can reproduce Briq’s exact canonical JSON; prefer **raw body only**.

### Edge cases

| Situation            | What to do                                                                                                                |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Secret configured    | Require `X-Briq-Signature` and verify it                                                                                  |
| No secret configured | Signature header is absent; you cannot verify with HMAC—decide whether to accept only signed webhooks in your environment |
| Bad signature        | Respond with **4xx** (recommended) or any **non-2xx**; Briq will retry according to its retry policy                      |

### Example (Python)

```python theme={null}
import hmac
import hashlib

def verify_briq_signature(*, raw_body: bytes, secret: str, signature_header: str) -> bool:
    prefix = "sha256="
    if not signature_header.startswith(prefix):
        return False
    expected_hex = signature_header[len(prefix) :]
    digest = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(digest, expected_hex)
```

### Example (Node.js)

```javascript theme={null}
import crypto from "crypto";

function verifyBriqSignature(rawBodyBuffer, secret, signatureHeader) {
  const prefix = "sha256=";
  if (!signatureHeader.startsWith(prefix)) return false;
  const expectedHex = signatureHeader.slice(prefix.length);
  const digest = crypto.createHmac("sha256", secret).update(rawBodyBuffer).digest("hex");
  try {
    return crypto.timingSafeEqual(Buffer.from(digest, "utf8"), Buffer.from(expectedHex, "utf8"));
  } catch {
    return false;
  }
}
```

***

## Idempotency and event `id`

The same logical event may be **delivered more than once** if your server returns an error, times out, or Briq retries after a network failure. The envelope `id` is stable for that delivery attempt’s payload.

**Recommendation:** Deduplicate using `id`, or a composite such as `id` + `event` + `data.message_id`. For **application correlation** between your send handler and webhook processing, use **`data.job_id`** (and **`data.recipient`** or **`data.message_id`** when one instant send has multiple recipients).

***

## SMS events

### Correlating webhooks with the send response

When you call **`POST /v1/message/send-instant`** (or another send endpoint that returns a job id), a successful response includes a **`job_id`**. Store it for the in-flight operation.

Webhook payloads include **`data.job_id`** for submit-time events (`sms.sent`, submit-time `sms.failed`). That value is **the same** as the **`job_id`** returned by the send API for that job, so you can match asynchronous callbacks to the HTTP request that queued the send.

For **batch instant** sends (multiple recipients in one request), expect **one webhook per recipient** for submit-time events: **`data.job_id`** is typically **shared** across those callbacks, while **`data.message_id`** and **`data.recipient`** differ per message.

**DLR events** (`sms.delivered`, `sms.failed`, `sms.expired`) are keyed in this guide primarily by **`data.message_id`**, which is the stable id for message log and detail APIs. Use **`job_id`** to tie submit-time webhooks back to the send response; use **`message_id`** (and `event`) to follow a single message through delivery outcomes.

### Product semantics

* **`sms.sent`** — Briq (or the upstream provider) has accepted the message for routing after submit. This is **not** the same as confirmed delivery to the handset.
* **`sms.delivered`**, **`sms.failed`**, **`sms.expired`** — Reflect **delivery-report (DLR)** outcomes when Briq processes a final or terminal status from the carrier path.

### When you might not get a DLR event

Briq only emits DLR webhooks when the reported status maps to **delivered** (`sms.delivered`) or **failed / expired** (`sms.failed`, `sms.expired`). Intermediate states (e.g. still pending at the carrier) do not produce those events until a qualifying update exists.

### Submit-time events

Emitted when the send pipeline finishes its submit step for your message.

| `event`      | When it fires                                                       |
| ------------ | ------------------------------------------------------------------- |
| `sms.sent`   | Submit outcome is success (message accepted for sending / routing). |
| `sms.failed` | Submit outcome is not success.                                      |

**`data` object (submit-time path, e.g. `sms.sent`):**

| Field                   | Type   | Description                                                                                                           |
| ----------------------- | ------ | --------------------------------------------------------------------------------------------------------------------- |
| `job_id`                | string | Same id as the **`job_id`** in the send API success response; use it to join webhooks to the request that returned it |
| `message_id`            | string | Briq message id for this recipient (once assigned)                                                                    |
| `recipient`             | string | Destination number (digits with country code, e.g. `255758786077`)                                                    |
| `status`                | string | Submit pipeline status label (e.g. `SENT`); treat as an opaque enum                                                   |
| `submitted_to_provider` | string | When present: UTC ISO-8601 with milliseconds and `Z`                                                                  |

**Example `data` for `sms.sent`:**

```json theme={null}
{
  "job_id": "instant--c0646f43-13c5-4258-bb16-3def2d4c16e8-1776068436.219251",
  "message_id": "3058704e-d2af-409e-ae5d-dab2ac0f88c5",
  "recipient": "255758786077",
  "status": "SENT",
  "submitted_to_provider": "2026-04-01T18:50:18.723Z"
}
```

### DLR (delivery report) events

Emitted when Briq applies a delivery report and maps it to a terminal-style outcome.

| `event`         | When it fires                                                                                      |
| --------------- | -------------------------------------------------------------------------------------------------- |
| `sms.delivered` | Carrier / pipeline reports successful delivery (mapped to a **SENT** outcome in Briq’s model).     |
| `sms.expired`   | Message failed and the carrier response indicates **expiry** (e.g. not delivered within validity). |
| `sms.failed`    | Message failed without an expiry classification, or generic failure.                               |

**`data` object (DLR path):**

| Field          | Type   | Description                                     |
| -------------- | ------ | ----------------------------------------------- |
| `message_id`   | string | Briq message id                                 |
| `status`       | string | Status after the update (e.g. `SENT`, `FAILED`) |
| `delivered_at` | string | On `sms.delivered`: UTC ISO-8601 ms + `Z`       |
| `expired_at`   | string | On `sms.expired`: UTC ISO-8601 ms + `Z`         |
| `failed_at`    | string | On `sms.failed` (DLR): UTC ISO-8601 ms + `Z`    |

Exact `status` strings match Briq’s internal lifecycle labels; treat them as opaque enums and branch on `event` first.

***

## What your endpoint must return

| Your HTTP response                      | Briq behavior                                                                                               |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| Any **2xx**                             | Treated as **success**; Briq stops retrying this attempt.                                                   |
| Non-2xx, timeout, or connection failure | Treated as **failure**; Briq retries with **exponential backoff** until a maximum attempt count is reached. |

The **response body** is not interpreted by Briq for success. **204 No Content** or **200 OK** with an empty body is sufficient.

### Typical retry behavior

Unless your Briq environment is configured differently, defaults are often in this range:

| Parameter       | Typical value                                                          | Meaning                            |
| --------------- | ---------------------------------------------------------------------- | ---------------------------------- |
| Max attempts    | `5`                                                                    | Delivery attempts per event        |
| Request timeout | `10` seconds                                                           | How long Briq waits per attempt    |
| Backoff         | Exponential from \~`30` s, capped around `3600` s, with jitter \~`15%` | Delay before retry after a failure |

**Exact limits** are set on the Briq side. If you need numbers for SLAs or capacity planning, ask **Briq support** or your account contact for your deployment.

***

## Security checklist

1. **HTTPS only** — Webhook URLs must use `https://`. Briq rejects non-HTTPS URLs and common unsafe targets (e.g. `localhost`, addresses that resolve to private or loopback networks) at registration time.
2. **Verify `X-Briq-Signature`** when your webhook has a secret.
3. **Check `app_id`** (and `X-Briq-App-ID`) match the app you expect.
4. **Replay policy** — The payload includes `created_at`. Briq does not publish a standard replay window for integrators; if you need time-based rejection, define your own rule (e.g. ignore events older than N minutes) and document it internally.

***

## Worked example (`sms.sent`)

Your path and host will differ; proxy headers (e.g. `X-Forwarded-*`) depend on your edge.

```http theme={null}
POST /briq/webhook HTTP/1.1
Host: your-api.example.com
Content-Type: application/json
Content-Length: 340
User-Agent: Briq-Webhook/1.0
X-Briq-App-ID: 425eee45-fd0f-4092-83bb-f45c026249a1
X-Briq-Service-Type: sms
X-Briq-Signature: sha256=fd9ce98cc9c49c30eeb41ffde33d2c2a774333f6ae4474b0194ee9fa856a844b
```

Pretty-printed body (Briq sends **compact** JSON; verify signatures on the wire format):

```json theme={null}
{
  "app_id": "425eee45-fd0f-4092-83bb-f45c026249a1",
  "channel": "sms",
  "created_at": "2026-04-01T18:50:20.334Z",
  "data": {
    "job_id": "instant--c0646f43-13c5-4258-bb16-3def2d4c16e8-1776068436.219251",
    "message_id": "3058704e-d2af-409e-ae5d-dab2ac0f88c5",
    "recipient": "255758786077",
    "status": "SENT",
    "submitted_to_provider": "2026-04-01T18:50:18.723Z"
  },
  "event": "sms.sent",
  "id": "evt_01KN563PSEBAQNPWEA6JSWQ6KS"
}
```

Return **2xx** after you have validated and accepted the event (or queued it internally).

***

## Optional: inspecting deliveries

Some Briq setups expose **dashboard or API** features to list webhook delivery attempts, view errors, or trigger retries. Availability, URLs, and authentication are **deployment- and product-specific**. Use whatever your Briq operator documents—do not rely on internal or test-only routes unless explicitly provided to you.

***

## Related guides

* [Developer apps](/guides/developer-apps) — API keys and app configuration in Briq.
* [Messaging](/guides/messaging) — Sending SMS and delivery concepts.
* [WhatsApp delivery events](/Karibu-WhatsApp/index#webhook-events): `whatsapp.sent` / `delivered` / `read` / `failed` / `received` events and signature verification.
* [Webhook Management API](/Karibu-Webhooks/index): Register webhooks, reveal secrets, and inspect or retry deliveries.
