Webhooks
webhooks:managev3.2.0STABLEWebhooks
Webhooks let TechWriter push real-time notifications to your server the moment something happens — a document is published, a comment is posted, a deployment fails. Instead of polling the API for changes, you register an HTTPS endpoint and TechWriter delivers a signed JSON payload within milliseconds of each event.
Every delivery includes an HMAC-SHA256 signature so you can cryptographically confirm the payload came from TechWriter and hasn't been tampered with. The SDK ships a WebhookVerifier helper that handles the constant-time comparison and replay-window check for you.
Pick your language on the right to see the full flow: registering an endpoint, verifying signatures, and dispatching events. All snippets assume your signing secret is in the TW_WEBHOOK_SECRET environment variable.
Event catalog
Subscribe to any combination of these events when you register a webhook. Use ["*"] to receive everything.
document.publishedDocumentA document transitions to Published status. Fired once per publish — not on re-publishes unless content changed.document.updatedDocumentA draft or published document is saved. High-frequency. Consider filtering on data.object.status.document.archivedDocumentA document is moved to the archive. The document is soft-deleted. Content is still readable via API.document.deletedDocumentA document is permanently deleted. Hard delete. Content cannot be recovered after this event.comment.createdCommentA new inline or document-level comment is posted. Includes author_id, body, and optional mention list.comment.resolvedCommentA comment thread is marked resolved. data.object.resolved_by contains the resolver's user ID.project.createdProjectA new project (workspace root) is created. Useful for provisioning downstream resources automatically.project.deletedProjectA project and all its documents are deleted. Cascade — listen for this if you mirror project structure.user.invitedUserA new user is invited to the workspace. Fires before the user accepts. data.object.status = pending.user.role_changedUserA user's role or group membership changes. Old and new role are both included in data.object.deployment.createdDeploymentA new portal deployment is triggered. Use this to kick off downstream CDN invalidations.deployment.failedDeploymentA portal deployment fails to complete. data.object.reason contains a human-readable failure summary.Delivery headers
Every POST request TechWriter sends includes these headers. Verify the signature first — then use the others for routing and idempotency.
X-TechWriter-Signaturet=<unix>,v1=<hex>Timestamp and HMAC-SHA256 of the raw payload. Always verify this before processing.X-TechWriter-Eventdocument.publishedThe event type string. Identical to event.type in the body — useful for routing in proxies.X-TechWriter-Deliveryevt_01HW8N4Z3R8C9XUnique delivery ID for this attempt. Stable across retries — use it for idempotency checks.X-TechWriter-Version2026-04-01Payload schema version. Pin to this in your handler to avoid surprise breaks on schema updates.Content-Typeapplication/jsonAlways JSON. Read the raw body bytes before parsing — signature verification requires the raw form.User-AgentTechWriter-Webhook/1.0Identifies TechWriter as the sender. Use in firewall rules to allow-list inbound requests.Retry schedule
TechWriter retries on any non-2xx response or connection timeout. The delivery ID stays constant across all attempts — use it as your idempotency key.
ImmediateNon-2xx or connection refusedFirst delivery attempt fires as soon as the event is emitted.5 secondsNon-2xx or timeout on attempt 1Short delay — catches brief restarts and cold-start latency.30 secondsNon-2xx or timeout on attempt 2After this point the event is considered degraded.5 minutesNon-2xx or timeout on attempt 3Long pause — allows maintenance windows to complete.30 minutesNon-2xx or timeout on attempt 4Final attempt. Failure triggers a webhook.delivery_failed notification.Webhook configuration
Options accepted by tw.webhooks.create() and the REST endpoint. All names are consistent across every SDK language.
urlstringrequiredHTTPS endpoint that receives POST requests. Plain HTTP is rejected at registration.eventsstring[]requiredOne or more event types to subscribe to. Pass ["*"] to receive all events in the workspace.secretstringrequiredSigning secret used to generate the X-TechWriter-Signature header. Store alongside your API key.activebooleanoptionalWhether deliveries are enabled. Defaults to true. Set to false to pause without deleting.descriptionstringoptionalA human-readable label shown in the dashboard. Useful when you run multiple endpoints.metadataobjectoptionalUp to 16 arbitrary key-value pairs for your own bookkeeping. Not included in the payload.timeoutnumberoptionalResponse timeout in seconds. Default 30. Increase for handlers that fan out to slow downstream services.Security checklist
Webhooks are an inbound attack surface. These seven rules cover the most common failure modes seen in production integrations.
Where to go next
1. Register a webhook
import { TechWriterClient } from ;
const tw = new TechWriterClient({ apiKey: process.env.TECHWRITER_API_KEY });
const webhook = await tw.webhooks.create({
url: ,
events: [, , ],
secret: process.env.TW_WEBHOOK_SECRET,
active: true
});
console.log(, webhook.id);2. Verify the signature
import { WebhookVerifier } from ;
import express from ;
const app = express();
const verifier = new WebhookVerifier(process.env.TW_WEBHOOK_SECRET);
"color: ">#8b949e">// Use raw body — JSON.parset verify correctly.
app.post(, express.raw({ type: }), (req, res) => {
try {
const event = verifier.constructEvent(
req.body, "color: ">#8b949e">// raw Buffer
req.headers[] "color: ">#8b949e">// "t=...,v1=..."
);
"color: ">#8b949e">// event is verified — safe to process
handleEvent(event);
res.sendStatus(200);
} catch (err) {
console.error(, err.message);
res.sendStatus(400);
}
});3. Handle events
async function handleEvent(event) {
switch (event.type) {
case : {
const doc = event.data.object;
await searchIndex.upsert({ id: doc.id, title: doc.title });
break;
}
case : {
await searchIndex.delete(event.data.object.id);
break;
}
case : {
const { authorId, body } = event.data.object;
await notifyMentions(authorId, body);
break;
}
default:
"color: ">#8b949e">// Unrecognised event — log and ignore, never 500.
console.log(, event.type);
}
}Pro tip
Respond with 200 immediately, then process the event in a background job. If your handler calls a slow downstream service synchronously, TechWriter will time out at 30 s and retry — leading to duplicate processing and retry storms during outages.