Skip to main content
The webhook connector POSTs a JSON payload to a URL you control whenever an article is published. Use it for a headless CMS, a static site generator, an internal review queue, or any CMS we don’t support natively.

Step 1 — Have an endpoint ready

You need a URL that accepts POST with a JSON body and answers with a 2xx status. Anything outside 200–299 counts as a failed delivery.
The endpoint must be reachable from the public internet. localhost works only while you are testing against a local SeoSorted install.

Step 2 — Connect in SeoSorted

Project Settings → Integrations → Webhook → Connect.
Connect Webhook dialog with Endpoint URL, Signing secret (optional) and Description (optional) fields

The webhook connect form

string
required
Where we POST, for example https://api.yourapp.com/seosorted/articles.
string
At least 16 characters. Leave it blank and we generate one for you — the usual choice.
string
A label for your own reference, e.g. “Production blog publisher”.
A secret shorter than 16 characters is rejected before anything is saved, with “Must be at least 16 characters — or leave it blank and we’ll generate one.”
Connect Webhook dialog with a five-character signing secret and an error saying the secret must be at least 16 characters

Short secrets are caught up front

Step 3 — Save the signing secret

If we generated the secret, it is shown once, immediately after connecting, as Webhook connected with a whsec_… value and a Copy button.
Webhook connected panel showing the generated whsec_ signing secret with a Copy button

Copy the signing secret — it is shown once

Store it in your app’s secret manager before clicking Done. We keep it encrypted and cannot show it again. Lost it? Connect the webhook again to generate a new one, then update your endpoint. Connecting again with the secret field blank always generates a new secret.

Step 4 — Send a test delivery

Open the Webhook card’s ⋮ menu and choose Send test. It posts a test event with a realistic payload and tells you exactly what your endpoint replied.
Integrations tab with the Webhook card connected, showing the endpoint URL and when it last synced

The connected Webhook card

You’ll see one of:

Step 5 — Publish

Open an article, click Publish, then Publish now next to Webhook. The dialog confirms Article Published — “Successfully delivered to your webhook endpoint”.
Publish dialog confirming the article was published and delivered to the webhook endpoint

A webhook publish confirms delivery

If your endpoint replies with JSON containing publishedUrl, url, link or permalink, we store that as the article’s live URL and the dialog shows a View article button. Relative paths are resolved against your endpoint URL.

What gets sent

The body carries the article’s title, slug, meta fields, featured image URL and the body as both Markdown and HTML — see the payload reference. The article’s JSON-LD schema is not included.

Re-publishing, unpublishing and deleting

  • Re-publish sends article.updated with the full article. Upsert on article.id so it replaces the post you stored at publish time.
  • Unpublish sends article.unpublished. Answer 2xx once the post is off your site; otherwise the article stays Published in SeoSorted. After a successful unpublish, the next publish sends article.published again.
  • Deleting the article in SeoSorted sends article.deleted. Nothing else is removed for you — your handler decides what to do.

Verifying the signature

Every request carries these headers: The signature is an HMAC-SHA256 of <t>.<raw request body> keyed with your signing secret, where t is the Unix timestamp in the same header.
Compute the HMAC over the raw request body, before any JSON parsing. Re-serializing the parsed object changes the bytes and the signature will never match.

Delivery behaviour

  • Timeout: 15 seconds. Reply quickly and do the slow work asynchronously.
  • Retries: article.published, article.updated and article.unpublished run through our job queue. A non-2xx reply or a timeout fails the attempt, and the job is retried up to 3 attempts in total with exponential backoff starting at 5 seconds. If every attempt fails, the publish (or unpublish) fails and the dashboard shows the error.
  • Fire-and-forget: article.deleted, article.updated sent on edit, and test are sent once and never retried.
  • Health: after 5 failed deliveries within an hour, the connector is marked Error. Its card then shows Retry instead of the ⋮ menu, and nothing is sent until you reconnect.
  • Duplicates: every attempt gets a new delivery id, so X-SeoSorted-Delivery is not an idempotency key. If your endpoint processed a request but replied too late, the retry arrives with a different id. Deduplicate on article.id + event (and make your handler an upsert).
See the payload reference for the exact JSON body.

Tracking AI crawlers

Track AI crawlers on the site that serves the published pages, not on the webhook receiver. Pick your framework and host in Which option to pick.

Common problems

Supply a longer signing secret, or leave the field blank and we’ll generate a strong one.
The test reached nothing, or your endpoint answered with a non-2xx status. The message after Test failed: names the cause — see the table under Step 4.
Almost always because the HMAC was computed over a re-serialized body. Sign the raw bytes exactly as received, and confirm you’re using the secret shown when the webhook was connected — reconnecting with a blank secret generates a new one.
Five or more deliveries failed within an hour, and the connector is in Error. Fix the endpoint, click Retry, and connect again — paste your existing signing secret to keep it.
A retry after a slow or failed reply. Deduplicate on article.id + event, not on the delivery id.