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
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: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
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: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
2xxquickly 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 theDoodocs-Event-Idheader, 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.
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. Passgrace_seconds: 0 and the
previous secret stops working at once.
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.
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.
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.