Webhooks
Chinaski can notify your other systems about what happens in the CMS by sending outbound webhooks. Four kinds exist: form submissions, post events, scheduled-content publications, and completed builds, each configured where it happens: per-form settings, the post-webhooks list, or build/publish settings in the admin area. This page covers what gets sent, how it’s protected, and what happens when a delivery fails.
How a delivery works
Outbound webhooks are HTTP JSON requests. The receiving endpoint gets:
- A JSON body (the event payload, or your custom body template with variables filled in).
Content-Type: application/json.- If you set a signing secret: an
X-Chinaski-Signature: sha256=…header containing an HMAC-SHA256 of the exact request body, so receivers can verify the request really came from Chinaski. - The HTTP method is POST by default; PUT and PATCH also work; anything else is sent as POST.
- Optional custom headers, one per line in
Name: valueform, as configured.
Where the events come from
- Form webhooks: sent after each form submission, configured per form (URL, signing secret, method, custom headers, custom body template).
- Post webhooks: you can register named targets under post-webhook settings, each subscribing to a subset of published, updated, deleted events. Sent for any matching post change. The payload includes the event name, post ID, slug, title, language, absolute URL of the post, published flag, author, category, tags, and timestamp.
- Publish webhooks: when scheduled content reaches its publish time, a single
content_publishedbatch payload is sent to the publish URL you configured, listing the items; it’s signed with your global webhook secret. - Build webhooks: after a successful build,
{status: 'ok', built_at: …, site_url: …, langs: […]}to the build webhook URL.
Old post-delivery URLs from previous setups that were never reconfigured continue to work: a legacy all-events URL keeps receiving all three post events unsigned.
Safety first
- Webhook targets must be public http/https addresses. Private/internal addresses (localhost, loopback, private IP ranges, Unicode-hostname encodings, and similar) are rejected outright; this prevents an editor from aiming webhooks at internal services.
- No redirects are followed.
- Timeouts are short (typically 5 s to connect, 10 s total), and responses larger than 64 KiB are cut off on the read path (publish and build webhooks read unlimited size at longer timeouts). Delivery failures don’t block content editing.
- A failing target is put on circuit-breaker cooldown: after 5 consecutive failures to the same URL, that URL is skipped for a full hour (any success resets the count). This protects Chinaski and your network from a dead endpoint.
Failures and retries
A non-2xx response (or connection failure) isn’t the end:
- Failed deliveries are retried automatically up to three times, after 1 minute, 10 minutes, then 1 hour.
- Retries run in a background worker that picks pending deliveries every 30 seconds; a claim is atomic, so two delivery passes never double-send the same attempt.
- You can also retry any delivery manually from the webhook views in the admin area.
Every attempt (success or failure) is recorded in a delivery log: the webhook type, target URL, the payload that was sent, the response status and body (response bodies are truncated at 10,000 characters), and an error message. Successful delivery records are pruned after 30 days; failed ones are kept so you can diagnose and manually retry them.
Demanding things to know in practice
- Outbound webhooks are capped at 8 concurrent requests; heavier batches queue their sends in order rather than dropping them.
- Air-gap mode (
CHINASKI_AIRGAP=1) disables all four webhook kinds as well as the automatic retry worker and manual retries: configure nothing if you never expect outbound traffic, since deliveries also can’t happen without air-gap mode being disabled. - Webhook signing secrets are stored encrypted in the database; leave the field blank on save to keep the existing secret.