Custom SEO publishing webhook for approved content

The custom webhook is the escape hatch for an in-house CMS or publishing service that has no native adapter. It sends one structured JSON payload containing the source post and normalized content, can sign the exact request body, and expects the tenant endpoint to return publishing evidence.

Best for: Teams with an existing server-side publishing endpoint that can authenticate requests, validate a stable payload, upsert by post ID or slug, and return a public URL.

Support status: stable
Publisher: webhook

How the connection works

Authentication

An HTTPS webhook URL with an optional bearer token and optional shared HMAC secret. All values stay in CI or another server-side secret environment.

Server-side variables

  • SEO_BLOG_PUBLISH_TOKEN
  • SEO_BLOG_SITE_SLUG
  • WEBHOOK_URL
  • WEBHOOK_TOKEN
  • WEBHOOK_SECRET
  • SEO_BLOG_SITE_ORIGIN

Publishing sequence

01

Build normalized Markdown, HTML, metadata, assets, canonical, and source-post fields.

02

Serialize one deterministic JSON request body and optionally sign it with HMAC-SHA256.

03

POST the payload to the tenant-controlled HTTPS endpoint.

04

Accept the returned id and publishedUrl only after a successful response.

No-write payload and mapping preview

SEO_BLOG_PUBLISH_TOKEN='<tenant-token>' \
SEO_BLOG_SITE_SLUG='<slug>' \
WEBHOOK_URL='<webhook-url>' \
WEBHOOK_TOKEN='<webhook-token>' \
WEBHOOK_SECRET='<webhook-secret>' \
SEO_BLOG_SITE_ORIGIN='<origin>' \
npx --yes @wotaso/seo-blog-admin-cli publish-cms --cms webhook --site-slug '<slug>' --site-origin '<origin>' --dry-run

For a currently due approved post, --dry-run builds the destination payload without writing to the provider. It does not contact the provider or prove credentials, remote schema compatibility, deployment, or a live page; when no post is due, provider inputs are not exercised.

Scheduled runner health heartbeat

SEO_BLOG_PUBLISH_TOKEN='<tenant-token>' \
SEO_BLOG_SITE_SLUG='<slug>' \
WEBHOOK_URL='<webhook-url>' \
WEBHOOK_TOKEN='<webhook-token>' \
WEBHOOK_SECRET='<webhook-secret>' \
SEO_BLOG_SITE_ORIGIN='<origin>' \
SEO_BLOG_SCHEDULED='true' \
npx --yes @wotaso/seo-blog-admin-cli health --config seo-blog.config.json --mode webhook --site-slug '<slug>' --site-origin '<origin>'

Run this from the real scheduled runner after seo-blog.config.json contains the selected connector configuration and the listed secrets are loaded. It reports configuration readiness, not a live article. The dashboard expires readiness at the returned deadline, with a 36-hour fallback when no deadline is available.

Fields and data model

  • Payload includes the original approved post and normalized content
  • The status field distinguishes draft and publish intent
  • Response may return id or postId and publishedUrl or url

Canonical and route

The tenant endpoint is responsible for durable storage, idempotent updates, rendering, canonical markup, sitemap changes, deployment, and URL verification. SEODrafts records the URL it returns.

Known limits

  • The receiving endpoint and its data contract are operated by the tenant.
  • A 2xx response without a durable URL is not proof that a public page is live.
  • Retries require an idempotent upsert keyed by the stable post ID or slug.

Recovery

  • Validate the payload and signature against a staging endpoint first.
  • Log request IDs and validation errors without logging tokens or shared secrets.
  • Return a deterministic publishedUrl after the destination has accepted the upsert.

Integration questions

Where are credentials stored?

The Custom webhook credentials stay in CI or another server-side secret environment.

Does SEODrafts own the canonical URL?

No. The destination website owns its route, canonical tag, sitemap, rendering, and analytics.

Can the integration be tested before publishing?

Use the no-write payload preview to inspect local mapping, then separately test credentials and the first destination draft before a live run.

Keep the website your team already owns.

Review the opportunity and draft in SEODrafts, then publish approved work through Custom webhook.

Add my website