POST /api/v1/webhooks · Operation ID: createWebhook
Compatibility note (2026-10-26): the published OpenAPI still lists this operation, but current working-tree code rejects new personal-account receivers with 403 webhooks_moved. New receivers belong to teams/classes and are created by an administrator through the organisation Developer tab. Previously created personal receivers continue to work in that code. This change has not been verified against the live runtime. The reference below preserves the published contract; do not assume new personal registration is available. Where creation is supported, store the returned signing secret immediately; it is shown only once. Maximum 10 endpoints. Document-limited tokens cannot register receivers. Validate signatures on raw bytes and durably deduplicate event IDs before processing.
Tokens limited to chosen documents can't register endpoints (403). Deliveries are signed: Handwrite-Signature: t=,v1=<hex HMAC-SHA256 of "t.body">.
Bearer token with webhooks:manage. Resource access and effective policy still apply.
Replace all REPLACE_* placeholders and environment values with data for your authorized test account. Use a new UUID in IDEMPOTENCY_KEY for each logical action where shown; use the actual latest revision for If-Match. Examples of writes change real data if run.
curl --fail-with-body -X POST 'https://handwrites.io/api/v1/webhooks' \
-H "Authorization: Bearer $HANDWRITE_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"url":"https://example.com/handwrite/webhook","events":["document.updated"]}'
application/json (required).
| Field | Required | Meaning |
|---|---|---|
url | Yes | https only, port 443 or 1024+, resolving only to public addresses. |
events | Yes | array |
| Status | Meaning | Body format |
|---|---|---|
| 201 | The endpoint and its secret, shown once. | application/json |
| 400 | invalid_request (the message names the field), validation_failed. | application/json |
| 401 | unauthorized: no token, or it's invalid, revoked or expired. | application/json |
| 403 | insufficient_scope, forbidden (you can see it but not do this), app_test_restricted, account_deleted (the account is scheduled for deletion; any request). | application/json |
| 422 | limit_reached, idempotency_mismatch, unreadable_file, no_text, empty_document. | application/json |
| 429 | rate_limited (burst or daily cap) or quota_exceeded (no characters left). | application/json |
| 500 | internal_error. | application/json |
Use the paired API reference for complete request/response schemas. See API specification notes for rate limits, error handling, safe retries and additional policy/plan failures. For binary or empty responses, do not call a JSON parser.