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

# Webhooks

> Instant notifications when data changes

Instead of polling, you can subscribe to events: when data in your tenant changes,
Doodocs People sends a `POST` to your URL.

<Note>
  The key scope for managing subscriptions is `webhooks:manage`. Delivery is
  at-least-once: retries are possible, so deduplicate on the `id` field.
</Note>

## Subscribe

```bash theme={null}
curl -X POST -H "X-API-Key: ddp_YOUR_KEY" -H "Content-Type: application/json" \
  https://app.doodocs.kz/api/developer/v1/webhook_endpoints \
  -d '{ "url": "https://your-server/hooks/doodocs", "event_types": ["employee.changed", "employee.deleted"] }'
```

The response returns the subscription `secret` — shown **once**. Store it: it verifies
the signature of every delivery.

## Thin payload

An event carries the type, the resource id, and the time — but not the data itself:

```json theme={null}
{
  "id": "evt_9f2c…",
  "type": "employee.changed",
  "occurred_at": "2026-02-10T09:30:00Z",
  "data": { "employee_id": "b7e4…" }
}
```

Fetch the current data with a normal request using your key:
`GET /developer/v1/employees/{employee_id}`. That way the key's scope and field
redaction apply automatically, and personal data never leaves for an external URL.

### Event types

| Type               | When                                                                                                               |
| ------------------ | ------------------------------------------------------------------------------------------------------------------ |
| `employee.changed` | An employee record was created or changed, **including a rename of the related person** (name/IIN/contact changes) |
| `employee.deleted` | An employee was deleted — a re-read returns `404`; the event is self-contained                                     |
| `person.changed`   | A person's identity fields (name, IIN) changed — useful when you need exactly those                                |

A person rename arrives as `employee.changed` for each affected employee — this is
the primary channel to keep names fresh (the `updated_since` filter on lists does not
reflect such changes).

Document events — `document.sent`, `document.completed`, `document.rejected`, and
others — have their own guide: [Document events](/en/guides/documents/events).

<Note>
  `employee.changed` covers both creation and change: on receiving it, re-read the
  resource and upsert. There is deliberately no separate `created` event — the source
  cannot always reliably tell a first appearance from a later change.
</Note>

## Verifying the signature

Every delivery carries a header:

```
Doodocs-Signature: t=1755244200,v1=5257a86…
```

where `v1` is `HMAC-SHA256(secret, "{t}.{request body}")` in hex. Verify it on your
side and reject the request if the signature does not match or `t` is older than five
minutes (replay protection).

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

def verify(secret: str, header: str, body: bytes) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t, v1 = parts["t"], parts["v1"]
    if abs(time.time() - int(t)) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)
```

## Delivery and reliability

* Respond `2xx` quickly and process asynchronously. The timeout is 10 seconds.
* On failure, the event is redelivered — up to **8 attempts** with exponentially
  growing backoff, roughly **a day** in total.
* After **20 consecutive** permanently failed deliveries the endpoint is
  disabled automatically and no new events are sent to it.
* Delivery is at-least-once: retries are possible. Use the event `id` to deduplicate.

## Operations

* **Re-enabling.** A disabled endpoint (after a run of failures, or manually) is
  re-enabled in the app settings: Integrations → Webhooks. There is currently no
  re-enable endpoint in the Developer API.
* **Events during downtime are not replayed.** While an endpoint is disabled,
  events do not queue up. After re-enabling, reconcile by polling:
  `GET /employees?updated_since=<disabled-at time>`.
* **Secret rotation.** The secret is issued once at creation and is not rotated.
  To replace it, create a new endpoint with the same URL and event types, switch
  your signature check to the new secret, then delete the old endpoint.

## Debugging

Send a test event to an existing subscription:

```bash theme={null}
curl -X POST -H "X-API-Key: ddp_YOUR_KEY" \
  https://app.doodocs.kz/api/developer/v1/webhook_endpoints/{id}/_test
```

The response contains the status code with which your server accepted the test
delivery.
