Skip to main content
Webhooks let you push change events to external services whenever pages are created, updated, or deleted. Each webhook receives a signed HTTP POST with details about the change.

Enabling webhooks

Add the [webhooks] section to your .kiwi/config.toml:
[webhooks]
enabled = true
max_workers = 4
max_retries = 3
KeyDefaultDescription
enabledfalseEnable the webhook subsystem.
max_workers4Concurrent dispatch goroutines.
max_retries3Retries on delivery failure.

Creating a webhook

Register a webhook URL with an optional glob pattern to filter which paths trigger it.
curl -X POST 'http://localhost:3333/api/kiwi/webhooks' \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/hook", "path_glob": "concepts/**"}'
{
  "id": "wh_abc123",
  "url": "https://example.com/hook",
  "path_glob": "concepts/**",
  "secret": "whsec_k9x2m...",
  "created_at": "2026-05-04T12:00:00Z",
  "enabled": true
}
The secret is only returned once at creation time. Store it securely — you’ll need it to verify webhook signatures.

Path glob patterns

PatternMatches
**All files (default)
concepts/**All files under concepts/
reports/*.mdMarkdown files directly in reports/
Glob matching uses Go’s filepath.Match with an added ** recursive wildcard.

Payload format

Every webhook receives a JSON POST body:
{
  "type": "write",
  "path": "concepts/auth.md",
  "actor": "agent:docs-writer",
  "timestamp": "2026-05-04T12:05:00Z"
}
FieldDescription
typeEvent type: write, delete, bulk, import.
pathFile path that changed.
actorThe X-Actor header from the originating request.
timestampISO 8601 timestamp of the change.

Signature verification

Every webhook POST includes three headers for verification:
HeaderDescription
webhook-idUnique message ID for deduplication.
webhook-timestampUnix timestamp of the dispatch.
webhook-signaturev1,<base64> HMAC-SHA256 signature.
The signature is computed over {webhook-id}.{webhook-timestamp}.{body} using the webhook secret as the HMAC key.
import hmac, hashlib, base64

def verify(secret, msg_id, timestamp, body, signature):
    signed_content = f"{msg_id}.{timestamp}.{body}"
    expected = hmac.new(
        base64.b64decode(secret),
        signed_content.encode(),
        hashlib.sha256
    ).digest()
    return hmac.compare_digest(
        base64.b64encode(expected).decode(),
        signature.removeprefix("v1,")
    )

Managing webhooks

List all webhooks

curl 'http://localhost:3333/api/kiwi/webhooks'
The secret field is omitted from list responses for security.

Delete a webhook

curl -X DELETE 'http://localhost:3333/api/kiwi/webhooks/wh_abc123'
Last modified on May 4, 2026