23links
HomeSigned publishing webhooks
Documentation

Signed publishing webhooks

Understand outbound article delivery and the separate 23links inbound article endpoint.

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"
}

Turn your ideas into a content plan.

Keep your topics, articles and publishing workflow together in 23links.

Get started