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

# Partner webhooks

> Signed deliveries for the whole partner account: the envelope, the signature, the retries, and the delivery log by API.

One webhook covers your whole partner account. It is set in the partner console under **Integration**: an HTTPS URL and a signing secret, revealed on demand and rotatable there and nowhere else. Which events reach it is a subscription you choose in the console or with [`PUT /v1/partner/webhook`](/api-reference/partner/update-the-webhook); the catalogue, the defaults and the opt-in events are on the [Events](/partners/events) page, together with the feed you can poll instead of running a webhook at all.

Partner-provisioned applications have no per-application webhook. Their `verification.*` and `message.*` events reach your partner webhook instead, with your `external_id` in the data, once you opt in.

## Reading the configuration

[`GET /v1/partner/webhook`](/api-reference/partner/get-the-webhook) answers the URL, whether a secret exists, the low-balance threshold, the events that reach the URL, the default set and every event you may subscribe to. The secret is never returned. [`PUT /v1/partner/webhook`](/api-reference/partner/update-the-webhook) takes `events` and `low_balance_threshold_halalas`; a body naming `url` or `secret` is refused with a message pointing at the console.

## The envelope

The same envelope as every Tawked webhook:

```json theme={"dark"}
{
  "event": "service.approved",
  "data": {
    "service_id": "c2a10f3e-8b7a-4d2e-9c1a-1a2b3c4d5e6f",
    "application_id": "c2a10f3e-8b7a-4d2e-9c1a-1a2b3c4d5e6f",
    "external_id": "store_88",
    "status": "active"
  },
  "sent_at": "1788265862201"
}
```

Two more, as they arrive:

```json theme={"dark"}
{
  "event": "service.owner_accepted",
  "data": {
    "service_id": "c2a10f3e-8b7a-4d2e-9c1a-1a2b3c4d5e6f",
    "application_id": "c2a10f3e-8b7a-4d2e-9c1a-1a2b3c4d5e6f",
    "external_id": "store_88",
    "owner": { "name": "أحمد", "phone": "+966551234567", "accepted_at": "2026-09-17T09:12:00.000Z" }
  },
  "sent_at": "1789000000000"
}
```

```json theme={"dark"}
{ "event": "balance.low", "data": { "balance_halalas": 4200, "threshold_halalas": 10000 }, "sent_at": "1789000000000" }
```

## Deliveries

[`GET /v1/partner/webhook/deliveries`](/api-reference/partner/list-deliveries) is the delivery log the console shows, newest first: every webhook call with its `status` (`pending`, `delivered`, `failed`), the `attempts` so far, the last response code or error, and the next attempt when one is scheduled. Filter by `status` and `event`, page with `cursor`. [`POST /v1/partner/webhook/deliveries/{id}/retry`](/api-reference/partner/retry-a-delivery) gives a failed delivery one more attempt, to the URL configured now and with a fresh signature; anything but `failed` answers `409 not_retryable` with the current `status`.

```json theme={"dark"}
{
  "data": [
    { "id": "7e3c5d9b-0a4f-4c1e-9b8d-2f6a1c3e5d7b", "event": "service.suspended", "status": "failed", "attempts": 5, "last_status_code": 500, "last_error": "http_500", "next_attempt_at": null, "delivered_at": null, "created_at": "2026-09-26T10:00:00.000Z" }
  ],
  "next_cursor": null
}
```

## Signature and retries

Every delivery carries `tawked-timestamp` (milliseconds since the epoch, the same value as `sent_at`), `tawked-signature` (hex HMAC-SHA256 of `"{timestamp}.{raw body}"` with your secret) and `tawked-event`. Compute the HMAC over the raw request body before parsing it, compare in constant time, and reject timestamps older than a few minutes. The code on the [Webhooks](/webhooks#verifying-the-signature) page works unchanged with your partner secret.

Delivery retries on anything but a `2xx`: 5 attempts in all, right away and then after 1 minute, 5 minutes, 30 minutes and 2 hours. Answer `2xx` quickly and do your own work after acknowledging, or the delivery is retried.

<Note>
  `balance.low` fires once per crossing: on the charge whose before and after straddle your threshold, and again only after a top-up lifts the balance back above it. A threshold of 0 turns the alert off. Keep it high enough for a top-up to land before the balance reaches zero, since a send with an empty balance answers `402 insufficient_credits`.
</Note>
