9 routes
/webhooks
9 routes.
Event subscriptions. Payloads are THIN (ids only, never row content), because filling one would mean decrypting an account into a body that leaves the account for good.
- POST /v1/webhooks
- GET /v1/webhooks
- GET /v1/webhooks/{id}
- PATCH /v1/webhooks/{id}
- DELETE /v1/webhooks/{id}
- POST /v1/webhooks/{id}/rotate-secret
- POST /v1/webhooks/{id}/ping
- GET /v1/webhooks/{id}/deliveries
- POST /v1/webhooks/{id}/deliveries/{did}/redeliver
POST /v1/webhooks
Subscribe to events (the secret is shown once)
The URL must be https with no credentials, on port 443, not an inittasks.com host, and must resolve only to public addresses. That is checked here and again at every delivery, because a name that resolves publicly today can resolve to loopback tomorrow. At most 25 subscriptions per account (conflict beyond). 20 creates per hour per user, sliding; a refused URL does not use one, but every URL check counts against 60 per hour.
Scopes: webhooks:write
Request body
| field | type | notes | |
|---|---|---|---|
url |
string | required | max 2048 chars, min 1 |
events |
array of todo.created, todo.updated, todo.deleted, note.created, note.updated, note.deleted, note_folder.created, note_folder.updated, note_folder.deleted, note.linked, note.unlinked, note.place_added, note.place_removed, note.note_linked, note.note_unlinked, note_version.created, note_version.updated, note_version.deleted, container.created, container.updated, container.deleted, heading.created, heading.updated, heading.deleted, inbox.created, inbox.updated, inbox.deleted, tag.created, tag.updated, tag.deleted, filter.created, filter.updated, filter.deleted, attachment.created, attachment.updated, attachment.deleted, attachment_place.created, attachment_place.updated, attachment_place.deleted, webhook.ping, webhook.disabled |
required | max 41 items |
description |
string | optional | or null, max 256 chars |
Response
- 201 WebhookCreated. The subscription PLUS
secret, returned exactly once.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * conflict * payload_too_large * unsupported_media_type * validation_failed * rate_limited * internal
GET /v1/webhooks
List subscriptions
Every subscription. ⚠ NOT cursor-paged, so there is no next_cursor to follow.
Scopes: webhooks:write
Response
- 200 WebhookList. Every subscription this account has.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * rate_limited * internal
GET /v1/webhooks/{id}
One subscription
Scopes: webhooks:write
Response
- 200 Webhook. The subscription.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * rate_limited * internal
PATCH /v1/webhooks/{id}
Change url, events or active
Setting active: true is how a subscription the server auto-disabled is brought back, once its endpoint answers again. Deliveries that were waiting while it was off are NOT replayed: they are closed as failed (not sent: …) and can be redelivered one by one. A new url is checked like a create, against the 60 URL checks per hour.
Scopes: webhooks:write
Request body
| field | type | notes | |
|---|---|---|---|
url |
string | optional | max 2048 chars, min 1 |
events |
array of todo.created, todo.updated, todo.deleted, note.created, note.updated, note.deleted, note_folder.created, note_folder.updated, note_folder.deleted, note.linked, note.unlinked, note.place_added, note.place_removed, note.note_linked, note.note_unlinked, note_version.created, note_version.updated, note_version.deleted, container.created, container.updated, container.deleted, heading.created, heading.updated, heading.deleted, inbox.created, inbox.updated, inbox.deleted, tag.created, tag.updated, tag.deleted, filter.created, filter.updated, filter.deleted, attachment.created, attachment.updated, attachment.deleted, attachment_place.created, attachment_place.updated, attachment_place.deleted, webhook.ping, webhook.disabled |
optional | max 41 items |
active |
boolean | optional | |
description |
string | optional | or null, which clears it, max 256 chars |
Response
- 200 Webhook. The subscription after the change.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * payload_too_large * unsupported_media_type * validation_failed * rate_limited * internal
DELETE /v1/webhooks/{id}
Delete a subscription
Scopes: webhooks:write
Response
- 204 No body. Deleted.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * rate_limited * internal
POST /v1/webhooks/{id}/rotate-secret
New secret; the old one verifies for 24 h
Both secrets sign every delivery during the overlap, so a subscriber can deploy the new one without dropping the events that arrive mid-deploy. Expect TWO v1= values in the signature header until previousSecretValidUntil.
Scopes: webhooks:write
Response
- 200 WebhookRotated. The subscription, the NEW secret, and when the previous one stops verifying.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * payload_too_large * unsupported_media_type * rate_limited * internal
POST /v1/webhooks/{id}/ping
Deliver a webhook.ping event
Sent whether or not events lists webhook.ping. 60 per hour per user. A subscription that is off answers conflict.
Scopes: webhooks:write
Response
- 202 PingQueued. Queued. The delivery has not happened yet; poll
/deliveriesfor the outcome.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * conflict * payload_too_large * unsupported_media_type * rate_limited * internal
GET /v1/webhooks/{id}/deliveries
Delivery attempts, newest first
Ordered (createdAt desc, id desc) and cursor-paged like every other list: ?limit= is 1–200, default 50, and next_cursor is null on the last page. One row per delivery of an event, holding its LAST attempt only. ⚠ The log is RETAINED, not archived: every delivery still waiting, plus the last 100 finished ones per subscription, and nothing older than 30 days. Paging walks what is retained, and a delivery swept between two pages is simply gone.
Scopes: webhooks:write
Query
| parameter | type | notes | |
|---|---|---|---|
limit |
integer | optional | min 1, max 200 |
cursor |
string | optional | max 4096 chars |
Response
- 200 DeliveryList. One page of attempts, newest first.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * validation_failed * rate_limited * internal
POST /v1/webhooks/{id}/deliveries/{did}/redeliver
Queue the same event again
A NEW delivery row carrying the SAME eventId, so the record of what happened the first time survives and the receiver’s own de-duplication still works. 60 per hour per user. A subscription that is off answers conflict.
Scopes: webhooks:write
Response
- 202 RedeliveryQueued. Queued.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * conflict * payload_too_large * unsupported_media_type * rate_limited * internal