Skip to main content
A complete reference for integrating Briq’s WhatsApp Business API. Send text, template, media, and interactive messages, read conversation history, list senders and templates, and receive real-time delivery events (sent -> delivered -> read / failed) on your own webhook - all scoped to a Developer App you own.
  • API surface: https://karibu.briq.tz/v1/whatsapp/* and https://karibu.briq.tz/v1/webhooks/*
  • Audience: backend engineers integrating WhatsApp messaging (notifications, OTPs, customer care, interactive flows).
  • Version: v1
If you only read one thing, read Section 5 Behavioral rules - the 24-hour window and the template requirement are the two rules that trip up most integrations.

1. Overview

The Briq WhatsApp API is a stateless HTTP service that lets your backend:
  • Send four kinds of outbound messages: freeform text, pre-approved templates, media (image/document/video), and interactive messages (buttons/lists).
  • Read your WhatsApp data: senders (your business numbers), templates (approved blueprints), and conversations (threads with their messages and 24-hour window state).
  • Receive delivery events - whatsapp.sent, whatsapp.delivered, whatsapp.read, whatsapp.failed - pushed to a webhook URL you register.
Briq routes your messages through an underlying provider (Meta Cloud API or Infobip) and normalizes everything behind one consistent API. You never talk to the provider directly, and the webhook event names are identical regardless of which provider delivered the message.

Available today vs. upcoming

CapabilityStatus
Send text / template / media / interactiveAvailable
Mark inbound messages as readAvailable
List senders, templates, conversations, and conversation messagesAvailable
Register webhooks and receive sent / delivered / read / failed eventsAvailable
Inspect & retry webhook deliveriesAvailable
Receive inbound customer messages on your webhook (whatsapp.received)Upcoming - poll the Conversations API meanwhile
Native WhatsApp test webhook event (the test endpoint currently emits sms.sent)Upcoming
Bulk / campaign sends via APIUpcoming

2. Prerequisites

  • A Briq account with a workspace, and WhatsApp enabled for that workspace (at least one approved sender number / WABA configured). New senders are onboarded via the Briq dashboard.
  • An active developer API key (X-API-Key). Generate one in the Briq dashboard.
  • A Developer App linked to a workspace. Every WhatsApp endpoint is workspace-scoped; a key with no workspace is rejected with 403.
  • At least one approved template if you intend to start conversations or message users outside the 24-hour window.
  • Recipient phone numbers in E.164 digits-only format - no +, no spaces (e.g. 255712345678).

3. Authentication & host

Base URL

https://karibu.briq.tz
All endpoints are versioned under /v1/.

Required headers

HeaderRequiredNotes
X-API-KeyyesYour developer API key. Must be active and not expired. Missing/invalid -> 401.
X-App-IDnoA specific Developer App UUID. If your key is bound to an app, then - when you send X-App-ID - it must match that app, or the request is rejected with 403.
Content-Typeyes (POST/PATCH)application/json
HostyesMust be karibu.briq.tz (or docs.briq.tz). Other hosts -> 403 outside development/sandbox.

Workspace requirement

Every WhatsApp endpoint resolves your workspace from the app linked to your API key. If the key is not linked to a workspace you receive 403 (WORKSPACE_REQUIRED on message endpoints; a raw {"detail": "API key is not linked to a workspace."} on read endpoints). All senders, templates, conversations, and messages you can see are scoped to that one workspace.

4. Response conventions

Briq’s WhatsApp surface uses two response shapes. Know which one each endpoint uses so you parse correctly.

Enveloped responses - the Messages endpoints

POST /v1/whatsapp/messages/* (send-text, send-template, send-media, send-interactive, and mark-read) return a standard envelope:
{
  "success": true,
  "data": { "...": "endpoint-specific payload" },
  "errors": null,
  "request_id": "f1e2d3c4-b5a6-7890-1234-567890abcdef"
}
On error:
{
  "success": false,
  "data": null,
  "errors": [
    { "code": "WINDOW_CLOSED", "message": "The 24-hour conversation window has closed. Use a template message instead.", "field": null }
  ],
  "request_id": "f1e2d3c4-b5a6-7890-1234-567890abcdef"
}
  • success - true for 2xx, false otherwise.
  • data - payload on success, null on error.
  • errors - array of {code, message, field} on error, null on success.
  • request_id - unique per response. Quote it when contacting support.
Body validation failures (422) for any /v1/whatsapp/* path are returned in this envelope with errors[].code = "VALIDATION_FAILED" and field set to the offending field.

Bare responses - the read endpoints

GET /v1/whatsapp/senders/*, GET /v1/whatsapp/conversations/*, and GET /v1/whatsapp/templates/* return the resource directly - a JSON array or object - with no envelope. Their errors use the raw framework shape:
{ "detail": "Sender not found in this workspace." }
The Webhook Management endpoints also use bare objects and {"detail": ...} errors.
Treat any non-2xx status as a failure regardless of body shape. On enveloped endpoints read errors[].code; on bare endpoints read detail.

5. Behavioral rules (read this first)

  1. The 24-hour customer-care window. WhatsApp only lets a business send freeform messages (text, media, interactive) within 24 hours of the customer’s last inbound message. Outside that window you must send an approved template. Briq enforces this: send-text / send-media / send-interactive return WINDOW_CLOSED (HTTP 422) when the window is closed (unless you pass check_window: false). send-template always works and opens/refreshes the window.
  2. Templates must be APPROVED. You can only send a template whose admin status is APPROVED. Sending a non-approved or unknown template returns TEMPLATE_NOT_APPROVED / TEMPLATE_NOT_FOUND_OR_UNAPPROVED.
  3. Sends are accepted, then confirmed asynchronously. A successful send returns 200 (status: "sent") when the provider acknowledged immediately, or 202 (status: "pending") when queued to Briq’s async engine. Treat both as success and rely on webhook events for the final outcome. The data.message_id returned by the send is the same id that appears in every subsequent webhook - use it to correlate.
  4. Webhook events only fire for messages sent via this API. delivered / read events are emitted only for messages your API key sent. Messages sent from the dashboard do not notify your webhook.
  5. Rate limits. Send endpoints are limited per API key (default 60 requests/minute). Exceeding returns 429 with a Retry-After header. Back off and retry. The limiter is fail-open: if Redis is unavailable, requests are allowed through.
  6. Recipients are digits-only E.164. e.g. 255712345678. A leading + is stripped automatically.

6. Quick start (5 minutes)

# 1. Find a sender (your WhatsApp business number) in your workspace.
curl -s https://karibu.briq.tz/v1/whatsapp/senders \
  -H "X-API-Key: YOUR_API_KEY"

# 2. Register a webhook so you receive delivery events.
curl -s -X POST https://karibu.briq.tz/v1/webhooks/ \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "app_id": "YOUR_APP_ID", "service_type": "whatsapp", "url": "https://api.yourdomain.com/briq/webhooks/whatsapp" }'

# 3. Fetch the signing secret (used to verify X-Briq-Signature on incoming events).
curl -s https://karibu.briq.tz/v1/webhooks/WEBHOOK_ID/secret \
  -H "X-API-Key: YOUR_API_KEY"

# 4a. Inside the 24h window: send a freeform text.
curl -s -X POST https://karibu.briq.tz/v1/whatsapp/messages/send-text \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "sender_id": "SENDER_UUID", "recipient": "255712345678", "body": "Hello from Briq!" }'

# 4b. Outside the window (or first contact): send an approved template.
curl -s -X POST https://karibu.briq.tz/v1/whatsapp/messages/send-template \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "sender_id": "SENDER_UUID", "template_name": "briq_otp", "recipient": "255712345678", "variables": { "otp": "654321" } }'
You will receive whatsapp.sent, then whatsapp.delivered, then whatsapp.read (or whatsapp.failed) on your webhook.

7. Messages API

All endpoints are mounted under /v1/whatsapp/messages and return the standard envelope. A successful send returns HTTP 200 (data.status = "sent") when the provider accepted immediately, or HTTP 202 (data.status = "pending") when queued; treat both as success.

Behaviors shared by all send endpoints

  • 200 vs 202. If a provider_message_id is present the message went out synchronously (200/sent). Otherwise it was queued (202/pending, provider_message_id: null), and the provider id is filled in later via webhook.
  • 24-hour window. send-text, send-media, send-interactive require an OPEN window unless check_window: false. send-template is not subject to the window.
  • Recipient normalization. recipient is digits-only E.164; a leading + is stripped.
  • Sender resolution. When sender_id is omitted (where allowed), the workspace’s first active sender is used.
  • Rate limiting. 60 req/min per key by default; 429 + Retry-After when exceeded.
  • Usage metering. Every successful send (and read receipt) is metered against your API key.

Common success payload (data)

FieldTypeDescription
message_idUUID | nullBriq’s internal conversation message id. Matches data.message_id in webhooks.
conversation_idUUID | nullThe conversation this message belongs to.
sender_idUUID | nullThe sender the message was dispatched from.
provider_message_idstring | nullProvider message id (wamid). Present on 200/sent; null on 202/pending.
statusstring"sent" (HTTP 200) or "pending" (HTTP 202).

7.1 POST /v1/whatsapp/messages/send-text - Send a freeform text

Freeform text can only be delivered inside an open 24-hour window, so this endpoint enforces the window by default.
FieldTypeRequiredConstraintsDescription
sender_idUUIDNoValid sender in your workspaceFalls back to the first active sender when omitted.
recipientstringYes4-20 chars, digits-only E.164Destination number.
bodystringYes1-4096 charsThe text content.
check_windowbooleanNoDefault trueSet false to skip the 24h window check.
Success (200):
{
  "success": true,
  "data": {
    "message_id": "f0e9d8c7-b6a5-4321-9876-543210fedcba",
    "conversation_id": "11112222-3333-4444-5555-666677778888",
    "sender_id": "8b1d6c2e-7c3a-4e2b-9f1a-2d4c6e8a0b12",
    "provider_message_id": "wamid.HBgLMjU1NzEyMzQ1Njc4FQIAERgS...",
    "status": "sent"
  },
  "errors": null,
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
A 202 response is identical except provider_message_id: null and status: "pending".
curl -X POST "https://karibu.briq.tz/v1/whatsapp/messages/send-text" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sender_id": "8b1d6c2e-7c3a-4e2b-9f1a-2d4c6e8a0b12",
    "recipient": "255712345678",
    "body": "Hello from Briq! Your order has shipped.",
    "check_window": true
  }'

7.2 POST /v1/whatsapp/messages/send-template - Send a pre-approved template

Templates are the only way to message a recipient outside the 24-hour window, so this endpoint is not gated by the window check. The named sender must own a template with the given name, and that template must be APPROVED by Meta.
FieldTypeRequiredConstraintsDescription
sender_idUUIDYesMust own the templateThe sender that owns the template.
template_namestringYes1-512 charsName of the approved template.
recipientstringYes4-20 chars, digits-only E.164Destination number.
variablesobject (map<string,string>)NoDefault {}Template variable values keyed by placeholder name.
callback_datastringNoMax 512 charsOpaque data you can correlate with webhooks.
curl -X POST "https://karibu.briq.tz/v1/whatsapp/messages/send-template" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sender_id": "8b1d6c2e-7c3a-4e2b-9f1a-2d4c6e8a0b12",
    "template_name": "order_update",
    "recipient": "255712345678",
    "variables": { "1": "Asha", "2": "TZ-90211" },
    "callback_data": "order-90211"
  }'
Use a template to (re)open a conversation, after which freeform messages become deliverable. Provide every required variable; missing keys cause TEMPLATE_PARAM_COUNT_MISMATCH. See Section 9 Templates API for how to map a template’s params onto variables.

7.3 POST /v1/whatsapp/messages/send-media - Send image, document, or video

Media is referenced by URL; the provider fetches it, so media_url must be publicly reachable. Requires an open 24h window by default.
FieldTypeRequiredConstraintsDescription
sender_idUUIDNoValid senderFalls back to the first active sender when omitted.
recipientstringYes4-20 chars, digits-only E.164Destination number.
media_kindstring (enum)YesIMAGE, DOCUMENT, VIDEOCase-sensitive.
media_urlstringYesMin length 1Publicly reachable URL of the asset.
captionstringNoMax 1024 charsOptional caption.
filenamestringNoMax 255 charsOptional filename (useful for documents).
check_windowbooleanNoDefault trueSet false to skip the window check.
curl -X POST "https://karibu.briq.tz/v1/whatsapp/messages/send-media" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sender_id": "8b1d6c2e-7c3a-4e2b-9f1a-2d4c6e8a0b12",
    "recipient": "255712345678",
    "media_kind": "DOCUMENT",
    "media_url": "https://files.example.com/invoices/INV-90211.pdf",
    "caption": "Your invoice",
    "filename": "INV-90211.pdf"
  }'

7.4 POST /v1/whatsapp/messages/send-interactive - Send an interactive message

Sends an interactive WhatsApp message (button/list) using a raw Meta interactive payload you supply. Requires an open 24h window by default.
FieldTypeRequiredConstraintsDescription
sender_idUUIDNoValid senderFalls back to the first active sender when omitted.
recipientstringYes4-20 chars, digits-only E.164Destination number.
interactiveobjectYesRaw Meta interactive payloadPassed through to the provider as-is.
check_windowbooleanNoDefault trueSet false to skip the window check.
curl -X POST "https://karibu.briq.tz/v1/whatsapp/messages/send-interactive" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sender_id": "8b1d6c2e-7c3a-4e2b-9f1a-2d4c6e8a0b12",
    "recipient": "255712345678",
    "interactive": {
      "type": "button",
      "body": { "text": "Confirm your appointment?" },
      "action": {
        "buttons": [
          { "type": "reply", "reply": { "id": "yes", "title": "Yes" } },
          { "type": "reply", "reply": { "id": "no", "title": "No" } }
        ]
      }
    }
  }'
Build the interactive object to Meta’s WhatsApp interactive message spec - it is passed through unchanged. A malformed payload is rejected by the provider as INFOBIP_FAILURE (503).

7.5 POST /v1/whatsapp/messages/{message_id}/read - Mark an inbound message as read

Sends a read receipt for an inbound message you received. Only inbound messages belonging to a sender in your workspace can be marked read. Always responds synchronously with HTTP 200 (no pending variant) and is not rate-limited. Path parameter: message_id (string, 1-355 chars) - the inbound wamid. Success (200):
{
  "success": true,
  "data": { "message_id": "wamid.HBgLMjU1NzEyMzQ1Njc4FQIAEhgU", "status": "read_receipt_sent" },
  "errors": null,
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
curl -X POST "https://karibu.briq.tz/v1/whatsapp/messages/wamid.HBgLMjU1NzEyMzQ1Njc4FQIAEhgU/read" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json"
CodeHTTPWhen
INVALID_MESSAGE422The message is outbound - read receipts are only valid for inbound messages.
MESSAGE_NOT_FOUND404No message with that id exists.
FORBIDDEN403The message does not belong to your sender/workspace.

8. Senders API

Senders are the WhatsApp business phone numbers configured for your workspace. Pick which sender to use by passing its sender_id; if omitted on a send, the workspace’s first active sender is used.
These read endpoints return bare models - no envelope. The list returns a JSON array; get-one returns a single object. Errors use {"detail": "..."}.

8.1 GET /v1/whatsapp/senders - List senders

Query parameters: is_active (bool, optional), is_default (bool, optional).
curl "https://karibu.briq.tz/v1/whatsapp/senders?is_active=true" \
  -H "X-API-Key: YOUR_API_KEY"
Success (200):
[
  {
    "id": "3f1c2b8a-9d4e-4a7b-bf2c-1e6a0d5c4b21",
    "user_id": "usr_8K2p9Qd",
    "whatsapp_account_id": "a02b7c19-4e8d-4c0a-9f31-2b6d5e8a1c44",
    "phone_number_id": "109876543210987",
    "display_number": "255700000001",
    "label": "Support line",
    "is_default": true,
    "is_active": true,
    "is_deleted": false,
    "created_at": "2026-01-15T09:32:10.123456Z",
    "updated_at": "2026-03-02T14:05:48.987654Z"
  }
]
Sender object fields
FieldTypeDescription
idUUIDUnique identifier. Use as sender_id when sending.
user_idstringOwner user_id.
whatsapp_account_idUUID | nullOwning WABA account ID.
phone_number_idstringProvider-side phone-number ID.
display_numberstringDisplay number (E.164 without +).
labelstring | nullOptional label.
is_defaultbooleanWhether this is the default sender.
is_activebooleanWhether eligible for sending.
is_deletedbooleanWhether soft-deleted.
created_at / updated_atdatetimeTimestamps.

8.2 GET /v1/whatsapp/senders/{sender_id} - Get one sender

Returns a single sender object (same shape). A sender_id belonging to another workspace returns 404 ({"detail": "Sender not found in this workspace."}).
curl "https://karibu.briq.tz/v1/whatsapp/senders/3f1c2b8a-9d4e-4a7b-bf2c-1e6a0d5c4b21" \
  -H "X-API-Key: YOUR_API_KEY"

9. Conversations API

A conversation is the message thread between one of your senders and a single recipient. The 24-hour window state (is_active) is tracked on the conversation. Since inbound webhook events are upcoming, polling these endpoints is the current way to read inbound messages and observe window state. These endpoints are read-only and return bare models; errors are raw {"detail": "..."}.

9.1 GET /v1/whatsapp/conversations - List conversations

Query parameters: sender_id (UUID), is_active (bool), limit (1-100, default 50), offset (>=0, default 0). Success (200):
{
  "items": [
    {
      "id": "b1d8f2a0-3c4e-4a1b-9f2c-7d6e5a4b3c2d",
      "whatsapp_account_id": "a0c1e2f3-4567-8901-abcd-ef0123456789",
      "whatsapp_sender_number_id": "c2d3e4f5-6789-0123-abcd-ef0123456789",
      "recipient_phone": "+255712345678",
      "is_active": true,
      "last_message_at": "2026-05-30T10:15:30.000000Z",
      "created_at": "2026-05-28T08:00:00.000000Z",
      "updated_at": "2026-05-30T10:15:30.000000Z"
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}
The wrapper is {items, total, limit, offset}. is_active indicates whether the 24h window is currently open (freeform sends allowed).
curl "https://karibu.briq.tz/v1/whatsapp/conversations?is_active=true&limit=50&offset=0" \
  -H "X-API-Key: YOUR_API_KEY"

9.2 GET /v1/whatsapp/conversations/{conversation_id} - Get one conversation

Returns a single bare conversation object (same shape as a list item). Unknown IDs / other workspaces return 404.

9.3 GET /v1/whatsapp/conversations/{conversation_id}/messages - List messages

Returns messages (inbound and outbound) ordered by created_at ascending. Query parameters: limit (1-200, default 100), offset (>=0, default 0). Success (200):
{
  "items": [
    {
      "id": "f7e6d5c4-b3a2-4190-8f7e-6d5c4b3a2190",
      "message_id": "wamid.HBgMMjU1NzEyMzQ1Njc4FQIAERgSN0E...",
      "is_outbound": true,
      "message_type": "text",
      "content": { "body": "Hello! Your order has shipped." },
      "status": "delivered",
      "created_at": "2026-05-30T10:10:00.000000Z",
      "paired_message_id": null
    }
  ],
  "total": 2,
  "limit": 100,
  "offset": 0
}
FieldTypeDescription
idUUIDInternal message identifier.
message_idstringProvider id (wamid.*) or a local- prefixed id if not yet assigned.
is_outboundbooltrue if sent by your sender; false for inbound.
message_typestringe.g. text, template, image.
contentobjectPayload; shape depends on message_type.
statusstringpending, queued, sent, delivered, read, failed.
created_atdatetimeCreation time.
paired_message_idUUID | nullLinked message’s id, if any.
Since inbound webhook delivery is upcoming, poll this endpoint to read inbound (is_outbound: false) messages.

10. Templates API

Message templates are pre-registered, Meta-approved structures you must use to start a conversation or message a user outside the 24-hour window. Only templates whose admin status is APPROVED can be sent. These read endpoints return bare models.

10.1 GET /v1/whatsapp/templates - List templates

Cursor-paginated. Query parameters: sender_id (UUID), status (repeatable), category (repeatable: AUTH/AUTHENTICATION, UTILITY, MARKETING), language (repeatable, e.g. en_US), name_or_content (max 200 chars), cursor (opaque), limit (1-200, default 50), sort (updated_desc default, updated_asc, created_desc, name_asc, name_desc). Success (200):
{
  "items": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "order_confirmation",
      "language": "en_US",
      "category": "UTILITY",
      "params": ["customer_name", "order_id"],
      "details": { "components": [ { "type": "BODY", "text": "Hi {{1}}, your order {{2}} is confirmed." } ] },
      "status": "APPROVED",
      "is_deleted": false,
      "template_type": "standard",
      "parameter_format": "POSITIONAL",
      "quality_score": "GREEN",
      "whatsapp_sender_number_id": "9c1f0b2e-7d44-4a91-8f0a-1b2c3d4e5f60",
      "created_at": "2026-05-01T09:12:00Z",
      "updated_at": "2026-05-28T10:15:00Z",
      "approved_at": "2026-05-02T14:00:00Z",
      "last_sent_at": "2026-05-29T08:30:00Z",
      "rejection_reason_code": null,
      "rejection_reason_text": null
    }
  ],
  "cursor_next": "eyJ1cGRhdGVkX2F0IjoiMjAyNi0wNS0yOFQxMDoxNTowMCswMDowMCIsImlkIjoiM2ZhODVmNjQ...",
  "total_count": 137
}
Key fields
FieldTypeDescription
idUUIDTemplate identifier.
namestringThe template_name you pass to send-template.
languagestring | nulle.g. en_US.
categoryenum | nullAUTH, UTILITY, MARKETING.
paramsarray | nullOrdered placeholder names -> keys of the variables object.
detailsobject | nullComponent/placeholder layout.
statusenumDRAFT, IN_REVIEW, APPROVED, PAUSED, REJECTED, DISABLED, IN_APPEAL, PENDING_DELETION, DELETED, LIMIT_EXCEEDED, ARCHIVED. Only APPROVED is sendable.
whatsapp_sender_number_idUUID | nullThe sender that owns the template.
quality_scorestring | nullMeta quality rating (GREEN/YELLOW/RED).
approved_at / last_sent_atdatetime | nullLifecycle timestamps.
Cursor pagination: request a page, then if cursor_next is non-null pass it back as the cursor query param. Stop when cursor_next is null. Keep sort and filters identical across pages. Many additional lifecycle/meta fields are present on each item but are not needed for sending.
curl "https://karibu.briq.tz/v1/whatsapp/templates?status=APPROVED&limit=50" \
  -H "X-API-Key: YOUR_API_KEY"

10.2 GET /v1/whatsapp/templates/{template_id} - Get one template

Returns a single bare template (same shape). A 404 is returned both for unknown IDs and templates owned by another workspace.

10.3 Using a template to send

Map a listed APPROVED template’s fields onto the send-template body:
  • template_name: the template’s name
  • variables: an object keyed by the template’s ordered params
  • sender_id: the template’s whatsapp_sender_number_id
  • language: implied by the template you reference (not a send-body field)
{
  "sender_id": "9c1f0b2e-7d44-4a91-8f0a-1b2c3d4e5f60",
  "template_name": "order_confirmation",
  "recipient": "255700000000",
  "variables": { "customer_name": "Asha", "order_id": "A-10293" }
}

11. Webhook events (delivery lifecycle)

Briq pushes status events to your registered whatsapp webhook as a message you sent moves through its delivery lifecycle. To manage webhooks (register, secret, test, deliveries, retries) see the Karibu Webhooks API.

Prerequisites

  1. You have a registered whatsapp webhook for the app (one per app per service_type).
  2. The message was originally sent via the Developer API (only API-originated messages are tagged and correlated back to your app - dashboard sends do not notify your webhook).

The four events

EventMeaning
whatsapp.sentThe message left Briq and was accepted by the provider.
whatsapp.deliveredThe message reached the recipient’s device.
whatsapp.readThe recipient opened/read the message.
whatsapp.failedThe message could not be delivered (terminal).

Lifecycle

   POST /v1/whatsapp/... (send)
        |
        v
   200 sent  /  202 pending
        |
        v
 whatsapp.sent  ->  whatsapp.delivered  ->  whatsapp.read
        |
        +-->  whatsapp.failed   (terminal)
Briq enforces a status-rank guard so a message never regresses (a late sent cannot overwrite delivered; failed is terminal). Not every message produces all four events.

Common envelope

{
  "event": "whatsapp.sent",
  "event_id": "wa_3f1c9a2b7e8d4f6a9c0b1d2e3f4a5b6c",
  "app_id": "8a1f0c4e-2b3d-4e5f-9a8b-7c6d5e4f3a2b",
  "service_type": "whatsapp",
  "timestamp": "2026-05-30T11:02:14.512389+00:00",
  "data": {
    "message_id": "d6b1f0a2-7c3e-4d5a-9b8c-1e2f3a4b5c6d",
    "conversation_id": "b2c3d4e5-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
    "provider_message_id": "wamid.HBgLMjU1...",
    "recipient": "+255700000000"
  }
}
FieldTypeDescription
eventstringOne of the four event names.
event_idstringUnique id, prefixed wa_ + 32-char hex. Use for idempotency.
app_idUUIDThe developer app that owns the message and webhook.
service_typestringAlways "whatsapp".
timestampstring (ISO 8601)UTC time with +00:00 offset.
data.message_idUUIDThe Briq conversation-message UUID - identical to the send response’s data.message_id.
data.conversation_idUUIDThe conversation the message belongs to.
data.provider_message_idstring | nullProvider wamid; may be null.
data.recipientstring | nullRecipient phone number; may be null.
data.error_codestringOnly on whatsapp.failed.
data.error_messagestringOnly on whatsapp.failed.
A whatsapp.failed event adds error_code and error_message inside data:
{
  "event": "whatsapp.failed",
  "event_id": "wa_7d6c5b4a39281706f5e4d3c2b1a09f8e",
  "app_id": "8a1f0c4e-2b3d-4e5f-9a8b-7c6d5e4f3a2b",
  "service_type": "whatsapp",
  "timestamp": "2026-05-30T11:02:31.665018+00:00",
  "data": {
    "message_id": "d6b1f0a2-7c3e-4d5a-9b8c-1e2f3a4b5c6d",
    "conversation_id": "b2c3d4e5-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
    "provider_message_id": "wamid.HBgLMjU1...",
    "recipient": "+255700000000",
    "error_code": "131026",
    "error_message": "Message undeliverable"
  }
}

Verifying the signature

Every delivery carries an X-Briq-Signature header containing the HMAC-SHA256 of the raw request body, keyed by your webhook’s signing secret. Fetch the secret once via GET /v1/webhooks/{id}/secret and store it securely.
  • Compute the HMAC over the raw request bytes - do not re-serialize parsed JSON.
  • Use a constant-time comparison.
  • Reject the request if the signature is missing or does not match.
import hashlib
import hmac

def verify_briq_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header or "")

# In your handler, read the RAW body (not request.json):
#   raw = request.get_data()
#   sig = request.headers.get("X-Briq-Signature", "")
#   if not verify_briq_signature(raw, sig, WEBHOOK_SECRET):
#       abort(401)

Idempotency & ordering

Webhook delivery is at-least-once and not strictly ordered.
  • Idempotency: dedupe on event_id (retried deliveries reuse it).
  • Ordering: do not assume sent arrives before delivered; events may arrive out of order.
  • Never regress status on your side: once read, don’t downgrade to delivered/sent; once failed, treat as terminal.
  • Key off data.message_id to attach each event to the correct message (matches your send response).
Each emitted event is persisted as a delivery with max_attempts = 3 and retried on non-2xx. Respond quickly with a 2xx, then process asynchronously. Inspect and replay via the Webhook Management endpoints.

Upcoming

  • Inbound customer messages (whatsapp.received) - not yet delivered; poll the Conversations API meanwhile.
  • Native WhatsApp test event - POST /v1/webhooks/{id}/test currently emits a synthetic sms.sent, not a whatsapp.* event.

12. Error reference

HTTP status codes

StatusMeaning
200Success. For sends: provider acknowledged immediately (status: "sent").
202Accepted. Send queued to the async engine (status: "pending"); await webhook.
204Success, no content (webhook delete).
400Bad request - unusable input (no sender, template params mismatch, duplicate webhook).
401Missing or invalid X-API-Key.
403Workspace not linked, X-App-ID mismatch, or wrong Host.
404Resource not found in your workspace / not owned by you.
422Validation failed, window closed, template not approved, or invalid state transition.
429Rate limit exceeded. Honor Retry-After.
503Provider/temporarily unavailable or unexpected server error - safe to retry with backoff.

Send error codes (enveloped errors[].code)

CodeHTTPWhenWhat to do
VALIDATION_FAILED422Body failed schema validationFix the indicated field.
WORKSPACE_REQUIRED403API key not linked to a workspaceLink the app to a workspace.
NO_ACTIVE_SENDER400No active sender (when sender_id omitted)Configure/activate a sender, or pass sender_id.
SENDER_NOT_FOUND404sender_id not in your workspaceUse a valid sender from the Senders API.
SENDER_INACTIVE400Sender exists but is disabledRe-enable it or choose another.
NO_SENDER / NO_USER_SENDER400 / 404Resolved sender missing or inactiveVerify the sender.
NO_TEMPLATE_LINK404Sender does not own a template with that nameUse a template the sender owns.
NO_DEFAULT_SENDER400No active sender with that approved templateConfigure an approved template on a sender.
TEMPLATE_NOT_APPROVED422Template not yet approved by MetaWait for approval / pick an approved template.
TEMPLATE_NOT_FOUND_OR_UNAPPROVED404Template unknown or not approvedCheck the name and approval status.
TEMPLATE_PARAM_COUNT_MISMATCH400variables don’t match the template’s paramsSend exactly the template’s params.
TEMPLATE_PAUSED_OR_DISABLED422Template paused/disabled (quality)Use another template; fix quality.
WINDOW_CLOSED42224-hour window closed for a freeform sendSend an approved template to reopen.
RECIPIENT_UNDELIVERABLE422Number unreachableVerify the recipient.
PHONE_NOT_REGISTERED422Number not on WhatsAppConfirm the user has WhatsApp.
AUTH_FAILURE503Provider auth problemRetry; contact support if persistent.
RETRYABLE_PROVIDER503Transient provider errorRetry with backoff.
INFOBIP_FAILURE503Provider could not processRetry with backoff.
RATE_LIMIT_EXCEEDED429Too many requests for this keyHonor Retry-After.
SERVICE_UNAVAILABLE503Unexpected server errorRetry with backoff.

mark-read error codes

CodeHTTPWhen
INVALID_MESSAGE422The message is outbound (you can only mark inbound messages read).
MESSAGE_NOT_FOUND404Unknown message_id.
FORBIDDEN403The message isn’t in your workspace.

13. Changelog

VersionNotes
v1Initial WhatsApp Developer API: send (text/template/media/interactive), mark-read, senders, templates, conversations, webhook management, and whatsapp.sent/delivered/read/failed events.
Upcoming: inbound message webhooks (whatsapp.received), a native WhatsApp test webhook event, and bulk/campaign sends.