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

# React to hire and termination

> Handle webhooks for employee hires and terminations

Instead of periodic polling, subscribe to events and update your copy as soon as an
employee is created or terminated at Keruen LLP. An event only tells you what changed
— you fetch the current data with your key.

<Note>
  Subscriptions are managed by the `webhooks:manage` scope, and re-reading records
  requires `employees:read`.
</Note>

<Steps>
  <Step title="Subscribe to events">
    Create an endpoint for employee hires and deletions. The `secret` in the response is
    shown **once** — store it in a secrets manager; it verifies the signature of every
    delivery.

    ```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://keruen.example/hooks/doodocs",
        "event_types": ["employee.changed", "employee.deleted"]
      }'
    ```

    <Note>
      `employee.changed` combines creation and change: there is deliberately no separate
      `created`. Handle the event as an upsert — the source cannot always reliably tell an
      employee's first appearance from a later change.
    </Note>
  </Step>

  <Step title="Verify the signature">
    Every delivery carries the `Doodocs-Signature: t=<unix>,v1=<hex>` header, where `v1`
    is `HMAC-SHA256(secret, "{t}.{request body}")` in hex. 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)
    ```
  </Step>

  <Step title="Re-read and upsert">
    The payload contains only `employee_id`. Fetch the record with your key — that way the
    key's scope and field redaction apply to the data, and personal data does not leave
    for an external URL.

    ```bash theme={null}
    curl -H "X-API-Key: ddp_YOUR_KEY" \
      "https://app.doodocs.kz/api/developer/v1/employees/3f2a9c7e-…?expand=person&expand=department"
    ```

    * `employee.changed` → re-read `GET /employees/{employee_id}` and upsert by `id`.
    * `employee.deleted` → a re-read returns `404` `EMPLOYEE_NOT_FOUND`. Do not treat this
      as an error: the event is self-contained, so mark the employee deleted.
  </Step>
</Steps>

## The handler

Deduplicate on the event `id` (delivery is at-least-once), respond `2xx` quickly, and
push the heavy work to the background.

```python theme={null}
import json, requests

BASE = "https://app.doodocs.kz/api/developer/v1"
HEADERS = {"X-API-Key": "ddp_YOUR_KEY"}
SECRET = "…"                                   # the stored subscription secret

def handle(headers, body: bytes):
    if not verify(SECRET, headers["Doodocs-Signature"], body):
        return 401
    event = json.loads(body)
    if seen(event["id"]):                     # deduplicate by event id
        return 200

    employee_id = event["data"]["employee_id"]
    if event["type"] == "employee.deleted":
        mark_deleted(employee_id)
        return 200

    resp = requests.get(f"{BASE}/employees/{employee_id}", headers=HEADERS,
                        params={"expand": ["person", "department"]})
    if resp.status_code == 404:               # race: deleted between the event and the re-read
        mark_deleted(employee_id)
    else:
        upsert(resp.json())                   # employee.changed = create + update
    return 200
```

<Warning>
  An employee may be deleted between `employee.changed` and your re-read — then
  `GET /employees/{id}` returns `404`. Treat this as a deletion, not a failure.
</Warning>

## Next

<Columns cols={2}>
  <Card title="Sync all employees" icon="users" href="/en/guides/recipes/sync-employees">
    Initial export and a full re-read to reconcile deletions.
  </Card>

  <Card title="Webhooks" icon="bolt" href="/en/api-reference/webhooks">
    All event types, delivery reliability, and debugging.
  </Card>
</Columns>
