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

# Webhooks

> Signed events when a verification is verified, fails or expires, and when a WhatsApp message changes status.

Webhooks are configured per application on its **Webhooks** tab: a URL and a secret, revealed on demand and rotatable. Every delivery is signed, retried with backoff on anything but a `2xx`, and logged attempt by attempt in the dashboard.

## Events

| Event                                                                 | Fires from                                                           |
| --------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `verification.verified`                                               | `check`                                                              |
| `verification.failed`                                                 | `check`                                                              |
| `verification.expired`                                                | The expiry sweep, or any `check` or `GET` that discovers the expiry  |
| `message.sent`, `message.delivered`, `message.read`, `message.failed` | The WhatsApp status sync, within about a minute of Meta reporting it |

## The envelope

```json theme={null}
{
  "event": "verification.verified",
  "data": {
    "id": "b7e5c2b0-9c1a-4e2f-8f2a-3a6b0e9d1c44",
    "status": "verified",
    "application_id": "c2a10f3e-8b7a-4d2e-9c1a-1a2b3c4d5e6f",
    "reference": "order-42",
    "channel": "sms",
    "to": "+966551234567",
    "mode": "live",
    "attempts": 1,
    "expires_at": "2026-09-01T12:34:56.789Z",
    "verified_at": "2026-09-01T12:31:02.145Z",
    "created_at": "2026-09-01T12:29:56.789Z"
  },
  "sent_at": "1788265862201"
}
```

WhatsApp events use the same envelope with the message's fields under `data`, plus `occurred_at` for the time of the status the event announces, and `error` set only on `message.failed`. See [WhatsApp messages](/whatsapp/messages#statuses-and-webhooks).

## Verifying the signature

Every delivery carries three headers:

| Header             | Value                                                          |
| ------------------ | -------------------------------------------------------------- |
| `tawked-timestamp` | Unix time in milliseconds                                      |
| `tawked-signature` | Hex HMAC-SHA256 of `"{timestamp}.{raw body}"` with your secret |
| `tawked-event`     | The event name                                                 |

Compute the HMAC over the **raw** request body, before any JSON parsing, and compare it in constant time:

<CodeGroup>
  ```javascript Node.js theme={null}
  import { createHmac, timingSafeEqual } from 'node:crypto';

  export function verify(rawBody, headers, secret) {
    const expected = createHmac('sha256', secret)
      .update(`${headers['tawked-timestamp']}.${rawBody}`)
      .digest('hex');
    const given = headers['tawked-signature'] ?? '';
    return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
  }
  ```

  ```php PHP theme={null}
  $expected = hash_hmac('sha256', $request->header('tawked-timestamp').'.'.$request->getContent(), $secret);

  abort_unless(hash_equals($expected, (string) $request->header('tawked-signature')), 401);
  ```

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

  def verify(raw_body: bytes, headers: dict, secret: str) -> bool:
      msg = f"{headers['tawked-timestamp']}.".encode() + raw_body
      expected = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
      return hmac.compare_digest(expected, headers.get("tawked-signature", ""))
  ```
</CodeGroup>

<Warning>
  Reject deliveries whose timestamp is older than a few minutes to close the replay window, and answer `2xx` quickly. Do your own work after acknowledging, or the delivery is retried.
</Warning>

Partner-provisioned applications receive partner webhooks instead; client webhooks are not configurable on them yet.
