Connect your receiver
In Integrations, save the destination URL and a shared publish token of at least 16 characters. Set automatic publication deliberately. The test sends {"event":"test","sent_at":"<ISO timestamp>"}; your receiver should acknowledge a valid ping with 2xx.
Authenticate the exact bytes
Outbound POST requests include Authorization: Bearer <publish_token> and X-Signature: sha256=<hex HMAC-SHA256>. Compute the HMAC over the raw JSON request body with the same token and compare it in constant time. Do not parse and re-encode the body before verification.
Handle article delivery
The example shows the outbound article shape. keyword, cluster, translation_of_external_id and some metadata can be null. Delivery retries mean your receiver must be idempotent. An unpublish delivery adds action: "unpublish"; remove or retire the matching destination article. Return 2xx only after successful handling.
The separate inbound endpoint
POST /api/webhooks/articles is an operator-configured inbound endpoint. It uses services.article_webhook.secret for both Bearer authentication and HMAC, and services.article_webhook.target_site_id to select a site. It upserts by external_id. It is not a general multi-tenant public API or a receiver for test pings.
Inbound validation and responses
Inbound required fields: external_id (integer), title (maximum 500), slug (lowercase letters, digits and hyphens; maximum 250), status (draft, published, scheduled or archived), content, canonical_path (maximum 500), language (maximum 10), meta (array), keyword and cluster (strings, maximum 250), published_at (date). Missing or invalid values return 422. Authentication failures return 401; missing operator configuration returns 500. Success returns ok, external_id, slug and article_id.
{
"external_id": 123,
"title": "A practical content guide",
"slug": "practical-content-guide",
"status": "published",
"content": "# A practical content guide\nArticle Markdown…",
"summary": "A short description of the guide.",
"tags": ["content"],
"format": "markdown",
"canonical_path": "/blog/practical-content-guide",
"blog": {"title": "Journal", "description": "Practical guides", "base_path": "/blog"},
"language": "en",
"meta": {"description": "A practical guide."},
"keyword": "content guide",
"cluster": "Content planning",
"translation_of_external_id": null,
"published_at": "2026-01-01T12:00:00+00:00"
}