# Guide to 8x8 Webhooks: Features and Best Practices

8x8 webhooks tell your systems what is happening to the messages you send and receive through 8x8 Connect, the CPaaS portal behind 8x8's SMS and Chat Apps APIs. A customer texts your number, a WhatsApp message is read, a delivery receipt comes back from a carrier, someone clicks a shortened link, or Meta changes the status of one of your WhatsApp templates. Each of these can reach your own service as a JSON request, so you can reply to customers, update message status and keep your template records in sync without polling.

This guide covers how 8x8 Connect webhooks are configured and authenticated, the event types and the two payload shapes you will see, 8x8's short retry schedule, and the practices that make an 8x8 receiver reliable.

## What are 8x8 webhooks?

In 8x8 Connect, a webhook belongs to a sub-account, or to every sub-account, and to a type: **SMS**, **Messaging Apps** or **Voice**. An SMS webhook receives both inbound SMS and SMS delivery receipts on one URL. Messaging Apps webhooks cover Chat Apps channels such as WhatsApp and Viber. This guide covers SMS and Chat Apps webhooks; Voice webhooks are configured through a separate API, and the Voice Call Action webhook expects a call flow in the response, which is a different integration pattern.

Every request is a `POST` with a JSON body and a top-level `eventType`. 8x8 does not sign these requests. Instead, each webhook has an optional **HTTP Authorization** value that 8x8 sends as the `Authorization` header on every request, exactly as you entered it. Comparing that header with the value you configured is how your receiver tells an 8x8 request from a forged one.

## 8x8 webhook features

| Feature | Details |
| --- | --- |
| Configuration | 8x8 Connect **Webhooks** page, or the Webhooks Configuration API |
| Scope | One sub-account, or all sub-accounts with `*` |
| Types | SMS (inbound and delivery receipts), Messaging Apps, Voice |
| Method and format | `POST`, JSON |
| Event field | `eventType` in the body |
| Authentication | Optional **HTTP Authorization** value (API field `httpAuthorization`), sent verbatim as `Authorization` |
| Other auth option | OAuth 2.0 client credentials, where 8x8 fetches a token from your token endpoint |
| Signature and timestamp | None documented for Connect webhooks |
| Retries | After 1, 10, 30 and 90 seconds on a connection error, timeout, `4xx` or `5xx` |
| Delivery receipts | 8x8 keeps checking with the carrier for up to 48 hours |
| Source IPs | No egress list published for SMS or Chat Apps webhooks |

8x8's IP Whitelisting page in Connect restricts which addresses can call the 8x8 API. It does not tell you where webhooks come from.

## Common events

| `eventType` | Envelope | Sent when |
| --- | --- | --- |
| `inbound_message_received` | Messaging | An SMS or Chat Apps message arrives for your number. `namespace` is `SMS` or `ChatApps` |
| `outbound_message_status_changed` | Messaging | A delivery receipt. `payload.status.state` is `queued`, `sent`, `delivered`, `undelivered`, `rejected` or `read` |
| `external_app_message` | Messaging | A message sent from the WhatsApp Business App is forwarded to your webhook |
| `short_url_clicked` | Messaging | A recipient clicks a shortened URL in an SMS |
| `template_status_update` | Business management | A WhatsApp or Viber template is approved, rejected, paused and so on |
| `template_category_update`, `template_quality_update`, `template_correct_category_detection` | Business management | A WhatsApp template's category or quality changes, or WhatsApp detects a better category |
| `account_update` | Business management | Your WhatsApp Business Account changes, refined by `eventDetails.event` |
| `phone_number_quality_update` | Business management | A WhatsApp number's messaging limit or throughput changes |
| `whatsapp_automatic_events` | Business management | Meta detects a purchase or lead in a conversation from a Click-to-WhatsApp ad |

Messaging events share one envelope: `namespace`, `eventType`, `description` and a `payload` object. Because SMS and Chat Apps use the same `eventType` values, route on `namespace` and `eventType` together. Here is 8x8's inbound SMS example:

```json
{
  "namespace": "SMS",
  "eventType": "inbound_message_received",
  "description": "SMS inbound message",
  "payload": {
    "umid": "9e09ac86-bd74-5465-851d-1eb5a5fdbb9a",
    "subAccountId": "SubAccount-1",
    "timestamp": "2016-01-01T14:34:56.017Z",
    "source": "+6581968289",
    "destination": "+4534735477",
    "body": "Test MO message",
    "encoding": "GSM7",
    "smsCount": 2
  }
}

```

Chat Apps Business Management Updates use a different, flatter shape with no `namespace` or `payload`: `eventId`, `timestamp`, `provider` (`WhatsApp` or `viber`), `businessAccountId`, `accountId`, `eventType` and `eventDetails`. A receiver that handles both needs to check which shape it has before reading fields. Business Management Updates and inbound SMS are not on by default. The Chat Apps webhook API only adds the business management type (`CABM`) when you ask for it, and inbound SMS has to be activated with your 8x8 account manager.

8x8 does not publish a list of every `eventType` in one place, and WhatsApp group events and SMS Engage responses are not covered by the list above. Acknowledge any event type you do not recognise.

> **Inspect 8x8 webhooks as they arrive.** Point an 8x8 Connect webhook at a [Hookdeck Inspect](https://inspect.hookdeck.com) URL to see the exact headers and envelope your account sends, with no account or setup required.

## Setting up 8x8 webhooks

Generate a long random token first, for example with `openssl rand -hex 32`, and decide on the full header value you want 8x8 to send, such as `Bearer <token>`.

1. Sign in to 8x8 Connect and open the **Webhooks** page.
2. Click **Add webhook**.
3. Choose the sub-account, or all sub-accounts, and the type: **SMS** or **Messaging Apps**.
4. Enter your HTTPS endpoint URL.
5. In **HTTP Authorization**, enter the full value, for example `Bearer <token>`. 8x8 sends it as the `Authorization` header without adding a prefix or encoding anything.
6. Save, and check that the webhook's status is enabled.

The same settings are available through the Webhooks Configuration API, where the value goes in `httpAuthorization` and `subAccountId` can be `*` for every sub-account. The API masks the value as `***` when you read webhooks back, so store it before you save.

To route through Hookdeck, create a Hookdeck source with the **8x8** type and use its URL as the webhook URL. The source has one field, **Authorization Header**: paste the HTTP Authorization value exactly as set in 8x8, including any `Bearer ` or `Basic ` prefix. Hookdeck compares the whole value. The OAuth authentication option is not supported, because 8x8 then sends a token it fetches from your token endpoint, which changes over time. The source accepts `POST`.

## Securing 8x8 webhooks

Compare the whole `Authorization` header with the value you configured, in constant time. Do not strip the `Bearer ` prefix, and do not base64-decode a `Basic` value: 8x8 sends the string you typed, so compare the string.

```javascript
const crypto = require('crypto');
const express = require('express');

// The full HTTP Authorization value, e.g. "Bearer 9QQhlsEb8VY".
const EXPECTED = process.env.EIGHT_X_EIGHT_AUTHORIZATION;

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

app.post('/webhooks/8x8', express.raw({ type: '*/*' }), (req, res) => {
  const received = req.get('authorization');
  if (!EXPECTED || typeof received !== 'string' || !safeEqual(received, EXPECTED)) {
    return res.sendStatus(401);
  }

  let event;
  try {
    event = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }

  // Queue the event, then route on event.namespace and event.eventType in a worker.
  res.sendStatus(200);
});

```

The same check in Python:

```python
import hmac
import os

EXPECTED = os.environ.get("EIGHT_X_EIGHT_AUTHORIZATION", "")  # e.g. "Bearer 9QQhlsEb8VY"

def verify_8x8_request(headers) -> bool:
    if not EXPECTED:
        return False  # never accept when unconfigured
    received = headers.get("authorization")
    if not received:
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(received.encode("utf-8"), EXPECTED.encode("utf-8"))

```

A static `Authorization` value authenticates the sender, not the body. It cannot detect a modified payload, and with no signature or timestamp a captured request can be replayed and still pass. Use HTTPS so the value cannot be read in transit, use a long random token, and change it in 8x8 and in your environment together if it might have leaked. If you leave **HTTP Authorization** empty, 8x8 sends no `Authorization` header and there is nothing to check, so always set one.

8x8's WhatsApp reference tells receivers to check an `X-8x8-Signature` header "if provided", but no Connect page says when it is sent, what it covers or how to verify it. Until 8x8 documents it, treat the `Authorization` value as the credential.

> **Make 8x8 webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) checks the HTTP Authorization value on every 8x8 request, accepts each delivery straight away so 8x8's short retry window is never used up, and queues every message event durably.

## 8x8 webhook limitations and pain points

### Four retries in about two minutes

**The Problem:** A deploy, a restart or a short outage on your side loses inbound messages and delivery receipts for good.

**Why It Happens:** 8x8 retries a failed delivery after 1, 10, 30 and 90 seconds. Once those attempts are used, the event is not sent again. 8x8's WhatsApp reference describes retries "with exponential backoff" instead, so the two pages disagree, but neither describes retries that last longer than a few minutes.

**Workarounds:**

* Keep the receiving endpoint separate from the code that processes events, so a deploy of one does not take down the other.
* Return `200` as soon as the event is stored, and process it afterwards.
* For delivery status, reconcile against 8x8's APIs for messages whose receipts never arrived.

**How Hookdeck Can Help:** Hookdeck accepts each 8x8 request as it arrives and stores it, then delivers it to your service with [retries](/docs/retries) on a schedule you choose. You can replay any event after an outage, so 8x8's retry window no longer decides what you keep.

### Errors from your own checks are retried too

**The Problem:** A rotated token, a bug that returns `400`, or a missing route produces a burst of identical requests, and then the events are gone.

**Why It Happens:** 8x8 treats any `4xx` or `5xx` response the same way as a timeout and retries it. A `401` from a stale credential is retried four times and then dropped, like any other failure.

**Workarounds:**

* When you rotate the HTTP Authorization value, accept both the old and new values for a short overlap.
* Log the header names that arrive (never the values) so you can see quickly whether `Authorization` is missing or just wrong.

**How Hookdeck Can Help:** Hookdeck's 8x8 source compares the value you paste. Hookdeck answers 8x8 with a `200` either way, but a request with a missing or wrong value is recorded as failed verification and never reaches your service, and you can see exactly what 8x8 sent.

### Two payload shapes on Chat Apps

**The Problem:** A handler written for messaging events crashes on a template or account update, or the reverse.

**Why It Happens:** Messaging events nest their data under `payload` and carry a `namespace`. Business Management Updates put `eventId`, `provider` and `eventDetails` at the top level and have no `payload`.

**Workarounds:**

* Check for `payload` or `eventDetails` before reading fields, and route on `eventType`.
* Give business management events their own webhook URL if you subscribe to them.

**How Hookdeck Can Help:** Hookdeck [filters](/docs/filters) can route events by `eventType` or by the presence of `namespace` to separate destinations, and [transformations](/docs/transformations) can normalise the two shapes before they reach your service.

### No documented sender IPs or signature

**The Problem:** You cannot allowlist 8x8's addresses or verify that a body is unchanged.

**Why It Happens:** 8x8 publishes no egress IP list for SMS or Chat Apps webhooks, and the only documented credential for Connect webhooks is the static `Authorization` value.

**Workarounds:**

* Rely on the `Authorization` value over HTTPS, and keep it out of logs.
* For actions with real consequences, treat the webhook as a notification and confirm the message status through 8x8's API.

**How Hookdeck Can Help:** Hookdeck gives you a stable HTTPS URL to put in 8x8 Connect and checks the `Authorization` value before anything reaches your service, so your own endpoint can sit behind your firewall.

## Best practices

### Store the full Authorization value

Keep `Bearer <token>`, or your exact `Basic` value, as one string and compare it whole. 8x8 masks the value in API responses, so record it when you set it.

### Deduplicate on the message and its status

Retries resend the same event. For messaging events, key on `payload.umid` plus `payload.status.state` for receipts, or `payload.umid` alone for inbound messages. For business management events, key on `eventId`. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Do not depend on order

8x8 does not document an ordering guarantee, and a retried receipt can arrive after a newer one. Store each status with its `payload.status.timestamp` and only move a message's state forward.

### Acknowledge fast and process asynchronously

With four retries in about two minutes, a slow handler turns into lost events. Verify the header, store the event, return `200`, and do the work afterwards. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

8x8 Connect sends SMS and Chat Apps events as unsigned JSON `POST` requests, authenticated only by the HTTP Authorization value you set on the webhook. Compare that whole value in constant time over HTTPS, handle both the messaging and business management shapes, deduplicate on `umid` or `eventId`, and plan for a retry schedule that ends after about two minutes.

[Hookdeck Event Gateway](https://hookdeck.com) checks the header, accepts each 8x8 request immediately, and keeps every event until your service has processed it, so a restart on your side does not cost you inbound messages or delivery receipts.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle 8x8 webhooks reliably in minutes.