# Webhook endpoints

> API reference · Wathiqaty Insurer API v1 (draft)

Where Wathiqaty sends events (see the Webhooks guide for payloads and signatures). Up to 5 endpoints per environment.

Base URL: `https://sandbox-api.wathiqaty.com/api/v1` (sandbox), `https://api.wathiqaty.com/api/v1` (production).

- List endpoints: `GET /partner/webhook-endpoints`
- Create an endpoint: `POST /partner/webhook-endpoints`
- Update an endpoint: `PATCH /partner/webhook-endpoints/{id}`
- Delete an endpoint: `DELETE /partner/webhook-endpoints/{id}`
- Rotate the signing secret: `POST /partner/webhook-endpoints/{id}/rotate-secret`
- Send a test event: `POST /partner/webhook-endpoints/{id}/test`
- List deliveries: `GET /partner/webhook-endpoints/{id}/deliveries`
- Redeliver an event: `POST /partner/webhook-deliveries/{id}/redeliver`

## List endpoints

`GET /partner/webhook-endpoints`

- Auth: Bearer token, scope `webhooks:manage`

### Request examples

```bash
curl -X GET "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

```js
const res = await fetch("https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
  },
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}`)
```

```python
import requests

res = requests.get(
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints",
    headers={
        "Authorization": f"Bearer {access_token}",
    },
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {payload['error']['code']}")
data = payload["data"]
```

```csharp
using var req = new HttpRequestMessage(HttpMethod.Get,
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints");
req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
using var res = await http.SendAsync(req);
var json = await res.Content.ReadAsStringAsync();
res.EnsureSuccessStatusCode();
```

### Response 200

Your endpoints.

```json
{
  "status": "success",
  "data": [
    {
      "id": "f0e1d2c3-b4a5-4968-8776-655443322110",
      "url": "https://core.insurer.example/wathiqaty/webhooks",
      "events": [
        "policy.issued",
        "policy.cancelled",
        "cancellation.requested"
      ],
      "enabled": true,
      "description": "Core policy system",
      "createdAt": "2026-11-01T10:00:00.000Z"
    }
  ],
  "error": null,
  "metadata": {
    "timestamp": "2026-11-05T09:15:02.481Z",
    "path": "/api/v1/partner/…",
    "method": "GET",
    "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77"
  }
}
```

## Create an endpoint

`POST /partner/webhook-endpoints`

- Auth: Bearer token, scope `webhooks:manage`
- `Idempotency-Key`: optional

The response carries `signingSecret` **once**. Store it in your secret manager.

### Body (JSON)

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | yes | Public `https://` URL. Private and loopback addresses are refused. |
| `events` | string[] | yes | Event types, or `["*"]` for all, current and future. |
| `description` | string | no | Up to 255 characters. |

### Request examples

```bash
curl -X POST "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"url":"https://core.insurer.example/wathiqaty/webhooks","events":["policy.issued","policy.cancelled","cancellation.requested"],"description":"Core policy system"}'
```

```js
const res = await fetch("https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    "url": "https://core.insurer.example/wathiqaty/webhooks",
    "events": [
      "policy.issued",
      "policy.cancelled",
      "cancellation.requested"
    ],
    "description": "Core policy system"
  }),
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}`)
```

```python
import requests, uuid

res = requests.post(
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
      "url": "https://core.insurer.example/wathiqaty/webhooks",
      "events": [
        "policy.issued",
        "policy.cancelled",
        "cancellation.requested"
      ],
      "description": "Core policy system"
    },
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {payload['error']['code']}")
data = payload["data"]
```

```csharp
using var req = new HttpRequestMessage(HttpMethod.Post,
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints");
req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
req.Headers.TryAddWithoutValidation("Idempotency-Key", Guid.NewGuid().ToString());
req.Content = new StringContent(
    """
    {
      "url": "https://core.insurer.example/wathiqaty/webhooks",
      "events": [
        "policy.issued",
        "policy.cancelled",
        "cancellation.requested"
      ],
      "description": "Core policy system"
    }
    """,
    Encoding.UTF8, "application/json");
using var res = await http.SendAsync(req);
var json = await res.Content.ReadAsStringAsync();
res.EnsureSuccessStatusCode();
```

### Response 201

The endpoint and its signing secret.

```json
{
  "status": "success",
  "data": {
    "id": "f0e1d2c3-b4a5-4968-8776-655443322110",
    "url": "https://core.insurer.example/wathiqaty/webhooks",
    "events": [
      "policy.issued",
      "policy.cancelled",
      "cancellation.requested"
    ],
    "enabled": true,
    "description": "Core policy system",
    "createdAt": "2026-11-01T10:00:00.000Z",
    "signingSecret": "whsec_5f3c9a…"
  },
  "error": null,
  "metadata": {
    "timestamp": "2026-11-05T09:15:02.481Z",
    "path": "/api/v1/partner/…",
    "method": "POST",
    "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77"
  }
}
```

### Errors

| Status | Code | When |
| --- | --- | --- |
| 422 | `validation_error` | Not https, a private address, or an unknown event type. |
| 409 | `webhook_endpoint_limit` | You already have 5 endpoints in this environment. |

Plus the generic errors any route can return: [Errors](/guides/errors.md).

## Update an endpoint

`PATCH /partner/webhook-endpoints/{id}`

- Auth: Bearer token, scope `webhooks:manage`

Re-enabling an endpoint that was disabled after failures does not resend old events; redeliver them from the delivery list.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | The endpoint's Wathiqaty id. |

### Body (JSON)

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | no | New URL. |
| `events` | string[] | no | Replaces the list. |
| `enabled` | boolean | no | Pause or resume deliveries. |
| `description` | string | no | Up to 255 characters. |

### Request examples

```bash
curl -X PATCH "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled":false}'
```

```js
const res = await fetch("https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}", {
  method: "PATCH",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "enabled": false
  }),
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}`)
```

```python
import requests

res = requests.patch(
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    },
    json={
      "enabled": False
    },
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {payload['error']['code']}")
data = payload["data"]
```

```csharp
using var req = new HttpRequestMessage(HttpMethod.Patch,
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}");
req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
req.Content = new StringContent(
    """
    {
      "enabled": false
    }
    """,
    Encoding.UTF8, "application/json");
using var res = await http.SendAsync(req);
var json = await res.Content.ReadAsStringAsync();
res.EnsureSuccessStatusCode();
```

### Response 200

The endpoint.

```json
{
  "status": "success",
  "data": {
    "id": "f0e1d2c3-b4a5-4968-8776-655443322110",
    "url": "https://core.insurer.example/wathiqaty/webhooks",
    "events": [
      "policy.issued",
      "policy.cancelled",
      "cancellation.requested"
    ],
    "enabled": false,
    "description": "Core policy system",
    "createdAt": "2026-11-01T10:00:00.000Z"
  },
  "error": null,
  "metadata": {
    "timestamp": "2026-11-05T09:15:02.481Z",
    "path": "/api/v1/partner/…",
    "method": "PATCH",
    "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77"
  }
}
```

### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `not_found` | Endpoint doesn't exist or isn't your company's. |

Plus the generic errors any route can return: [Errors](/guides/errors.md).

## Delete an endpoint

`DELETE /partner/webhook-endpoints/{id}`

- Auth: Bearer token, scope `webhooks:manage`

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | The endpoint's Wathiqaty id. |

### Request examples

```bash
curl -X DELETE "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

```js
const res = await fetch("https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}", {
  method: "DELETE",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
  },
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}`)
```

```python
import requests

res = requests.delete(
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}",
    headers={
        "Authorization": f"Bearer {access_token}",
    },
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {payload['error']['code']}")
data = payload["data"]
```

```csharp
using var req = new HttpRequestMessage(HttpMethod.Delete,
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}");
req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
using var res = await http.SendAsync(req);
var json = await res.Content.ReadAsStringAsync();
res.EnsureSuccessStatusCode();
```

### Response 204

Deleted. No body.

### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `not_found` | Endpoint doesn't exist or isn't your company's. |

Plus the generic errors any route can return: [Errors](/guides/errors.md).

## Rotate the signing secret

`POST /partner/webhook-endpoints/{id}/rotate-secret`

- Auth: Bearer token, scope `webhooks:manage`

For 24 hours every delivery carries two signatures (old and new), so you can deploy the new secret without dropping events.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | The endpoint's Wathiqaty id. |

### Request examples

```bash
curl -X POST "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}/rotate-secret" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

```js
const res = await fetch("https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}/rotate-secret", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
  },
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}`)
```

```python
import requests

res = requests.post(
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}/rotate-secret",
    headers={
        "Authorization": f"Bearer {access_token}",
    },
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {payload['error']['code']}")
data = payload["data"]
```

```csharp
using var req = new HttpRequestMessage(HttpMethod.Post,
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}/rotate-secret");
req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
using var res = await http.SendAsync(req);
var json = await res.Content.ReadAsStringAsync();
res.EnsureSuccessStatusCode();
```

### Response 200

The new secret, shown once.

```json
{
  "status": "success",
  "data": {
    "signingSecret": "whsec_a91e0b…",
    "previousSecretExpiresAt": "2026-11-06T09:15:02.000Z"
  },
  "error": null,
  "metadata": {
    "timestamp": "2026-11-05T09:15:02.481Z",
    "path": "/api/v1/partner/…",
    "method": "POST",
    "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77"
  }
}
```

### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `not_found` | Endpoint doesn't exist or isn't your company's. |

Plus the generic errors any route can return: [Errors](/guides/errors.md).

## Send a test event

`POST /partner/webhook-endpoints/{id}/test`

- Auth: Bearer token, scope `webhooks:manage`

Sends a signed `ping` event now and reports what your endpoint answered.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | The endpoint's Wathiqaty id. |

### Request examples

```bash
curl -X POST "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}/test" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

```js
const res = await fetch("https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}/test", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
  },
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}`)
```

```python
import requests

res = requests.post(
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}/test",
    headers={
        "Authorization": f"Bearer {access_token}",
    },
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {payload['error']['code']}")
data = payload["data"]
```

```csharp
using var req = new HttpRequestMessage(HttpMethod.Post,
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}/test");
req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
using var res = await http.SendAsync(req);
var json = await res.Content.ReadAsStringAsync();
res.EnsureSuccessStatusCode();
```

### Response 200

Your endpoint's answer.

```json
{
  "status": "success",
  "data": {
    "deliveryId": "0d0a7c9b-8e6f-4a5b-9c3d-2e1f0a9b8c7d",
    "status": 200,
    "latencyMs": 182
  },
  "error": null,
  "metadata": {
    "timestamp": "2026-11-05T09:15:02.481Z",
    "path": "/api/v1/partner/…",
    "method": "POST",
    "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77"
  }
}
```

### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `not_found` | Endpoint doesn't exist or isn't your company's. |

Plus the generic errors any route can return: [Errors](/guides/errors.md).

## List deliveries

`GET /partner/webhook-endpoints/{id}/deliveries`

- Auth: Bearer token, scope `webhooks:manage`

The last 30 days of attempts, newest first.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | The endpoint's Wathiqaty id. |

### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `eventType` | string | no | Only this event type. |
| `failed` | boolean | no | Only attempts that didn't get a 2xx. |
| `page` | integer | no | Page number, from 1. Default 1. |
| `size` | integer | no | Items per page, 1–100. Default 20. |
| `sortOrder` | string | no | Default `desc`. Use `asc` with `sortBy=updatedAt` for a change feed. One of: `asc`, `desc`. |

### Request examples

```bash
curl -X GET "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}/deliveries" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

```js
const res = await fetch("https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}/deliveries", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
  },
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}`)
```

```python
import requests

res = requests.get(
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}/deliveries",
    headers={
        "Authorization": f"Bearer {access_token}",
    },
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {payload['error']['code']}")
data = payload["data"]
```

```csharp
using var req = new HttpRequestMessage(HttpMethod.Get,
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-endpoints/{id}/deliveries");
req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
using var res = await http.SendAsync(req);
var json = await res.Content.ReadAsStringAsync();
res.EnsureSuccessStatusCode();
```

### Response 200

A page of delivery attempts.

```json
{
  "status": "success",
  "data": {
    "items": [
      {
        "id": "0d0a7c9b-8e6f-4a5b-9c3d-2e1f0a9b8c7d",
        "eventId": "evt_7f6c1b0e-3c2d-4c84-9a0e-2f3b1c5d7e90",
        "eventType": "policy.issued",
        "attempt": 2,
        "status": 503,
        "latencyMs": 10000,
        "responseSnippet": "Service Unavailable",
        "nextAttemptAt": "2026-11-05T09:51:00.000Z",
        "createdAt": "2026-11-05T09:21:00.000Z"
      }
    ],
    "meta": {
      "page": 1,
      "size": 20,
      "totalItems": 1,
      "totalPages": 1,
      "hasNextPage": false,
      "hasPreviousPage": false
    }
  },
  "error": null,
  "metadata": {
    "timestamp": "2026-11-05T09:15:02.481Z",
    "path": "/api/v1/partner/…",
    "method": "GET",
    "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77"
  }
}
```

### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `not_found` | Endpoint doesn't exist or isn't your company's. |

Plus the generic errors any route can return: [Errors](/guides/errors.md).

## Redeliver an event

`POST /partner/webhook-deliveries/{id}/redeliver`

- Auth: Bearer token, scope `webhooks:manage`

Sends the event again now, with the same event `id` (so your dedupe still works).

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | The delivery's Wathiqaty id. |

### Request examples

```bash
curl -X POST "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-deliveries/{id}/redeliver" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

```js
const res = await fetch("https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-deliveries/{id}/redeliver", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
  },
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}`)
```

```python
import requests

res = requests.post(
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-deliveries/{id}/redeliver",
    headers={
        "Authorization": f"Bearer {access_token}",
    },
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {payload['error']['code']}")
data = payload["data"]
```

```csharp
using var req = new HttpRequestMessage(HttpMethod.Post,
    "https://sandbox-api.wathiqaty.com/api/v1/partner/webhook-deliveries/{id}/redeliver");
req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
using var res = await http.SendAsync(req);
var json = await res.Content.ReadAsStringAsync();
res.EnsureSuccessStatusCode();
```

### Response 202

Queued.

```json
{
  "status": "success",
  "data": {
    "deliveryId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
  },
  "error": null,
  "metadata": {
    "timestamp": "2026-11-05T09:15:02.481Z",
    "path": "/api/v1/partner/…",
    "method": "POST",
    "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77"
  }
}
```

### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `not_found` | Delivery doesn't exist or isn't your company's. |

Plus the generic errors any route can return: [Errors](/guides/errors.md).
