Overview

The Webhooks API lets you create and manage outbound webhook subscriptions. When events occur in your HolyDocs project (deployments, page changes, feedback, etc.), HolyDocs sends HTTP POST requests to your configured URLs with event payloads. Use webhooks to trigger CI/CD pipelines, sync data, send notifications, or build custom integrations.

Base path: https://api.holydocs.com/api/v1/projects/:projectId/webhooks

All webhook management endpoints require authentication with the projects:write scope.

List Webhooks

Retrieve all webhook subscriptions for a project.

bash
GET /api/v1/projects/:projectId/webhooks

bash
curl "https://api.holydocs.com/api/v1/projects/$PROJECT_ID/webhooks" \ -H "Authorization: Bearer $HOLYDOCS_API_KEY"

Response

json
{ "data": [ { "id": "wh_abc123", "url": "https://your-server.com/hooks/holydocs", "events": ["deployment.completed", "deployment.failed"], "active": true, "secret": "whsec_****************************cdef", "createdAt": "2026-03-15T08:00:00Z", "updatedAt": "2026-03-15T08:00:00Z", "lastDelivery": { "status": 200, "event": "deployment.completed", "deliveredAt": "2026-04-11T09:45:00Z", "duration": 230 } }, { "id": "wh_def456", "url": "https://hooks.slack.com/services/T00/B00/xxxx", "events": ["feedback.received"], "active": true, "secret": "whsec_****************************ghij", "createdAt": "2026-03-20T12:00:00Z", "updatedAt": "2026-04-01T15:30:00Z", "lastDelivery": { "status": 200, "event": "feedback.received", "deliveredAt": "2026-04-10T14:23:00Z", "duration": 180 } } ]}

The secret field is masked in list responses. The full secret is only returned once when the webhook is created.

Create Webhook

Create a new webhook subscription with a target URL and a list of events to subscribe to.

bash
POST /api/v1/projects/:projectId/webhooks

Request Body

json
{ "url": "https://your-server.com/hooks/holydocs", "events": ["deployment.completed", "deployment.failed", "page.updated"], "description": "Notify CI/CD pipeline on deployments"}
FieldTypeRequiredDescription
urlstringYesHTTPS URL to receive webhook payloads (must use HTTPS)
eventsstring[]YesArray of event types to subscribe to (minimum 1)
descriptionstringNoHuman-readable description (max 200 characters)
secretstringNoCustom signing secret. If omitted, one is auto-generated.
activebooleanNoWhether the webhook is active (default: true)

Response

json
{ "data": { "id": "wh_new789", "url": "https://your-server.com/hooks/holydocs", "events": ["deployment.completed", "deployment.failed", "page.updated"], "description": "Notify CI/CD pipeline on deployments", "active": true, "secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6", "createdAt": "2026-04-11T10:00:00Z" }}

The full secret value is only returned in the create response. Store it securely -- you will need it to verify webhook signatures. If you lose the secret, delete the webhook and create a new one.

bash
curl -X POST "https://api.holydocs.com/api/v1/projects/proj_abc123/webhooks" \ -H "Authorization: Bearer hd_a1b2c3d4e5f67890a1b2c3d4e5f67890" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-server.com/hooks/holydocs", "events": ["deployment.completed", "deployment.failed"], "description": "CI/CD pipeline notifications" }'

Update Webhook

Update an existing webhook's URL, events, or active status.

bash
PATCH /api/v1/projects/:projectId/webhooks/:id

Path Parameters

ParameterTypeDescription
projectIdstringProject ID
idstringWebhook ID

Request Body

All fields are optional. Only include the fields you want to update:

json
{ "url": "https://new-server.com/hooks/holydocs", "events": ["deployment.completed", "deployment.failed", "feedback.received"], "active": true}
FieldTypeRequiredDescription
urlstringNoUpdated HTTPS URL
eventsstring[]NoUpdated event list (replaces the existing list)
descriptionstringNoUpdated description
activebooleanNoEnable or disable the webhook

Response

json
{ "data": { "id": "wh_abc123", "url": "https://new-server.com/hooks/holydocs", "events": ["deployment.completed", "deployment.failed", "feedback.received"], "active": true, "updatedAt": "2026-04-11T10:30:00Z" }}

Updating the events array replaces the entire list. To add a new event without removing existing ones, include all desired events in the array.

Delete Webhook

Permanently delete a webhook subscription.

bash
DELETE /api/v1/projects/:projectId/webhooks/:id

Response

json
{ "data": { "deleted": true, "id": "wh_abc123" }}

Send Test Event

Send a test event to verify your webhook endpoint is working correctly. The test payload uses realistic sample data matching the selected event type.

bash
POST /api/v1/projects/:projectId/webhooks/:id/test

Request Body

json
{ "event": "deployment.completed"}
FieldTypeRequiredDescription
eventstringNoEvent type to simulate (default: first event in the webhook's subscription list)

Response

json
{ "data": { "delivered": true, "statusCode": 200, "duration": 245, "responseBody": "{\"ok\":true}", "event": "deployment.completed" }}

If the endpoint returns a non-2xx status:

json
{ "data": { "delivered": false, "statusCode": 500, "duration": 1200, "responseBody": "Internal Server Error", "event": "deployment.completed", "error": "Endpoint returned HTTP 500" }}

Event Types

Subscribe to any combination of the following events:

Deployment Events

EventDescriptionPayload Fields
deployment.startedA build has started processingdeploymentId, projectId, branch, commitSha
deployment.completedA build completed successfullydeploymentId, projectId, branch, commitSha, pagesBuilt, duration
deployment.failedA build faileddeploymentId, projectId, branch, error

Page Events

EventDescriptionPayload Fields
page.createdA new page was added to the docspagePath, pageTitle, deploymentId
page.updatedAn existing page was modifiedpagePath, pageTitle, deploymentId, diff
page.deletedA page was removed from the docspagePath, pageTitle, deploymentId

Feedback Events

EventDescriptionPayload Fields
feedback.receivedA reader submitted page feedbackfeedbackId, pagePath, rating, comment

AI Events

EventDescriptionPayload Fields
assistant.conversation.completedAn AI chat conversation endedconversationId, messageCount, tokensUsed, satisfied
agent.job.completedAn agent job finished processingjobId, suggestionsCount, status

Webhook Payload Format

All webhook deliveries follow a consistent envelope format:

json
{ "id": "evt_abc123", "type": "deployment.completed", "projectId": "proj_xyz", "timestamp": "2026-04-11T09:45:00Z", "data": { "deploymentId": "dep_abc123", "branch": "main", "commitSha": "a1b2c3d4e5f6", "pagesBuilt": 42, "duration": 15000 }}

Headers

Every webhook delivery includes these headers:

HeaderDescription
Content-Typeapplication/json
X-HolyDocs-EventEvent type (e.g., deployment.completed)
X-HolyDocs-DeliveryUnique delivery ID for deduplication
X-HolyDocs-SignatureHMAC-SHA256 signature for verification
X-HolyDocs-TimestampUnix timestamp of the delivery

Verifying Signatures

Every webhook delivery is signed with your webhook secret using HMAC-SHA256. Always verify signatures to ensure payloads are authentic.

The signature is computed over the concatenation of the timestamp and the raw request body:

text
signature = HMAC-SHA256(secret, timestamp + "." + body)
javascript
import crypto from 'crypto';function verifyWebhook(req, secret) { const signature = req.headers['x-holydocs-signature']; const timestamp = req.headers['x-holydocs-timestamp']; const body = req.body; // raw string body // Reject requests older than 5 minutes to prevent replay attacks const age = Math.floor(Date.now() / 1000) - parseInt(timestamp); if (age > 300) { throw new Error('Webhook timestamp too old'); } const expected = crypto .createHmac('sha256', secret) .update(`${timestamp}.${body}`) .digest('hex'); if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) { throw new Error('Invalid webhook signature'); } return JSON.parse(body);}

Always use constant-time comparison (timingSafeEqual in Node.js, hmac.compare_digest in Python) when verifying signatures. Standard string comparison is vulnerable to timing attacks.

Retry Policy

Failed deliveries (non-2xx responses or timeouts) are retried with exponential backoff:

AttemptDelay
1st retry30 seconds
2nd retry2 minutes
3rd retry15 minutes
4th retry1 hour
5th retry4 hours

After 5 failed retries, the delivery is marked as permanently failed. If a webhook accumulates 50 consecutive failures, it is automatically deactivated. You can reactivate it via the PATCH endpoint after fixing the receiving server.

Each delivery attempt includes the same X-HolyDocs-Delivery header. Use this value for deduplication on your server, since a single event may be delivered more than once due to retries.

Webhook Limits by Plan

PlanWebhooks per ProjectEvents per Month
Free21,000
Starter510,000
Pro20100,000
Business501,000,000
EnterpriseUnlimitedUnlimited

Error Codes

CodeStatusDescription
NOT_FOUND404Project or webhook not found
VALIDATION_ERROR400Invalid URL (must be HTTPS), empty events array, or unknown event type
AUTH_ERROR401Missing or invalid authentication
FORBIDDEN403API key lacks projects:write scope
LIMIT_EXCEEDED429Webhook limit or monthly event limit exceeded
ALREADY_EXISTS409A webhook with this URL already exists for this project
Ask a question... ⌘I