Skip to main content
Instead of polling, you can subscribe to events: when data in your tenant changes, Doodocs People sends a POST to your URL.
The key scope for managing subscriptions is webhooks:manage. Delivery is at-least-once: retries are possible, so deduplicate on the id field.

Subscribe

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:
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

A person rename arrives only as person.changed; employee.changed does not carry it. Subscribe to both types to keep names fresh, because the updated_since filter on lists does not reflect renames.
Both types carry the same {"employee_id": …} payload. When a person holds several employee records, person.changed is delivered for each of them: re-read GET /employees/{employee_id} and upsert exactly as you do for employee.changed. Document events — document.sent, document.completed, document.rejected, and others — have their own guide: Document events.
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.

Verifying the signature

Every delivery carries a header:
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).

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. Deduplicate on the event id: the same value arrives in the Doodocs-Event-Id header, so you can detect a repeat without parsing the body.

Managing a subscription

Change the URL or the event set

PATCH /webhook_endpoints/{id} changes url, event_types, or both. Omitted fields keep their current value, and an empty event list also means “leave as is”. The secret is untouched. Changing the url also clears the consecutive-failure counter, so a fix does not inherit the dead host’s failures.

Rotate the secret

POST /webhook_endpoints/{id}/_rotate_secret issues a new secret and returns it once. The previous secret keeps working for a window you choose — 24 hours by default, set through grace_seconds (a week at most). Every delivery in that window carries two signatures.
A safe rotation runs in this order: accept both headers on your side, call the rotation, deploy the new secret, then go back to accepting only Doodocs-Signature. Finish before previous_secret_expires_at, after which the old secret is no longer used and verifying against it starts failing.

When a secret leaks

A graceful rotation leaves the leaked secret able to sign deliveries for the whole window, which is long enough to forge them. Pass grace_seconds: 0 and the previous secret stops working at once.
Exactly one previous secret is kept, so a second rotation inside an open window is refused with DEVELOPER.WEBHOOK_ROTATION_IN_PROGRESS (409). Wait for previous_secret_expires_at, or cut over at once with grace_seconds: 0.

Re-enable

POST /webhook_endpoints/{id}/_reenable clears an auto-disable and resets the failure counter. The app does the same thing: Settings → Integrations → “For developers” → Webhooks.
Events during downtime are not replayed on their own. While a subscription is disabled, events do not queue up. Failed attempts stay in the delivery log and can be sent again one by one; for a bulk catch-up, poll: GET /employees?updated_since=<last_success_at> and GET /documents?updated_since=<last_success_at>&order_by=DOCUMENT_ORDER_UPDATED_AT_ASC, where the last successful delivery time is the subscription’s last_success_at.

Delivery log

GET /webhook_endpoints/{endpoint_id}/deliveries lists delivery attempts, newest first: what was sent, when, and how your server answered. Entries are kept for 30 days and carry identifiers and outcomes only, never the payload.
event_id matches the Doodocs-Event-Id header and the payload’s id, so an entry lines up with your own log. The status filter takes WEBHOOK_DELIVERY_STATUS_SUCCEEDED or WEBHOOK_DELIVERY_STATUS_FAILED.

Sending an event again

POST /webhook_endpoints/{endpoint_id}/deliveries/{delivery_id}/_redeliver queues the same event again, which is how events lost to an outage are recovered.
The repeat carries the original event_id, so a receiver that deduplicates on it recognises the repeat instead of processing the event twice. A disabled subscription refuses redelivery with DEVELOPER.WEBHOOK_DELIVERY_NOT_REDELIVERABLE: re-enable it first.

Debugging

Send a test event to an existing subscription:
status_code is the code your server returned for the test delivery. If the connection failed, status_code is 0 and the reason is in error.