Skip to main content

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

AspectValue
MethodPOST
BodyJSON (see JSON envelope (top-level fields))
Content-Typeapplication/json

Outbound headers

Briq sends the following headers on each delivery attempt:
HeaderAlways presentPurpose
Content-TypeYesapplication/json
User-AgentYesBriq-Webhook/1.0
X-Briq-App-IDYesUUID of your Briq developer app
X-Briq-Service-TypeYesChannel, e.g. sms
X-Briq-SignatureOnly when a signing secret is configuredsha256=<hex> — HMAC-SHA256 over the raw request body (see 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.
FieldTypeDescription
idstringUnique id for this event (Briq uses an evt_-prefixed identifier)
eventstringEvent name, e.g. sms.sent, sms.delivered
channelstringLowercase channel, e.g. sms
app_idstringSame app UUID as in X-Briq-App-ID
created_atstringUTC timestamp, ISO-8601 with milliseconds and Z
dataobjectEvent-specific fields (see 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

SituationWhat to do
Secret configuredRequire X-Briq-Signature and verify it
No secret configuredSignature header is absent; you cannot verify with HMAC—decide whether to accept only signed webhooks in your environment
Bad signatureRespond with 4xx (recommended) or any non-2xx; Briq will retry according to its retry policy

Example (Python)

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)

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.
eventWhen it fires
sms.sentSubmit outcome is success (message accepted for sending / routing).
sms.failedSubmit outcome is not success.
data object (submit-time path, e.g. sms.sent):
FieldTypeDescription
job_idstringSame id as the job_id in the send API success response; use it to join webhooks to the request that returned it
message_idstringBriq message id for this recipient (once assigned)
recipientstringDestination number (digits with country code, e.g. 255758786077)
statusstringSubmit pipeline status label (e.g. SENT); treat as an opaque enum
submitted_to_providerstringWhen present: UTC ISO-8601 with milliseconds and Z
Example data for sms.sent:
{
  "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.
eventWhen it fires
sms.deliveredCarrier / pipeline reports successful delivery (mapped to a SENT outcome in Briq’s model).
sms.expiredMessage failed and the carrier response indicates expiry (e.g. not delivered within validity).
sms.failedMessage failed without an expiry classification, or generic failure.
data object (DLR path):
FieldTypeDescription
message_idstringBriq message id
statusstringStatus after the update (e.g. SENT, FAILED)
delivered_atstringOn sms.delivered: UTC ISO-8601 ms + Z
expired_atstringOn sms.expired: UTC ISO-8601 ms + Z
failed_atstringOn 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 responseBriq behavior
Any 2xxTreated as success; Briq stops retrying this attempt.
Non-2xx, timeout, or connection failureTreated 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:
ParameterTypical valueMeaning
Max attempts5Delivery attempts per event
Request timeout10 secondsHow long Briq waits per attempt
BackoffExponential 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.
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):
{
  "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.