Go to Integrations

Custom Webhook

Send finished articles to any platform that accepts HTTP requests — your own CMS, Zapier, Make, n8n, or a custom API your team builds.

Overview

1

When you publish an article via the Custom Webhook integration, SEOcraftAI sends a structured HTTP POST request to your configured endpoint.

2

The payload contains the article title, content (Markdown or HTML), keyword, SEO metadata, and a cover image URL.

!

Your endpoint must be publicly accessible over HTTPS. Localhost URLs will not work in production.

Setup

1

Go to Integrations -> Custom Webhook

2

Enter your endpoint URL — must start with https://

3

Select Auth Type: None, Bearer Token, or Custom Header. For Bearer Token paste your secret; for Custom Header enter the header name (e.g. X-API-Key) and value.

4

Click Send Test — SEOcraftAI POSTs a sample payload and shows the response status.

5

Click Save once the test succeeds.

i

The test payload has event set to "test" so your endpoint can distinguish test pings from real publishes.

Payload structure

Every publish sends this JSON body:json
{
  "event": "publish",
  "timestamp": "2026-01-01T00:00:00.000Z",
  "article": {
    "id": "f255c3e4-d3c9-4010-8998-462379beb0f3",
    "title": "Article Title",
    "slug": "article-title",
    "excerpt": "Short plain-text summary of the article…",
    "content_markdown": "# Article Title\n\nFull article in Markdown…",
    "content_html": "<h1>Article Title</h1><p>Full article in HTML…</p>",
    "images": ["https://app.SEOcraftAI.com/api/articles/{id}/cover"],
    "seo": {
      "meta_title": "Article Title",
      "meta_description": "First ~157 chars of article…",
      "keywords": ["target keyword"]
    },
    "published_at": "2026-01-01T00:00:00.000Z",
    "main_image_url": "https://app.SEOcraftAI.com/api/articles/{id}/cover"
  },
  "main_image": {
    "url": "https://app.SEOcraftAI.com/api/articles/{id}/cover",
    "alt": "Article Title"
  },
  "website": {
    "id": "site-uuid",
    "baseUrl": "https://yoursite.com"
  }
}

Payload fields

FieldTypeDescription
eventstring"publish" on real publishes, "test" on test pings
timestampISO 8601Time the webhook was dispatched
article.idUUIDStable identifier — store it to upsert on republish
article.titlestringArticle headline
article.slugstringSEO-friendly URL path component
article.excerptstringPlain-text summary (~160 chars), stripped of Markdown
article.content_markdownstringFull article body in Markdown
article.content_htmlstringFull article body converted to HTML
article.imagesstring[]All image URLs found in the article (cover first, then inline)
article.seo.meta_titlestringSEO title (equals article title)
article.seo.meta_descriptionstringAuto-generated meta description (~157 chars)
article.seo.keywordsstring[]Target SEO keywords; empty array if none set
article.published_atISO 8601Publication timestamp
article.main_image_urlstring | nullCover image URL — null if no image generated yet
main_image.urlstring | nullSame as article.main_image_url, for convenience
main_image.altstring | nullAlt text for the image (equals article title)
website.idUUIDYour SEOcraftAI site ID
website.baseUrlstringYour site's base URL as configured in SEOcraftAI

Handling the cover image

1

Read article.main_image_url from the payload. If it is null, the article has no cover image yet — skip the image step.

2

Fetch the image with a standard HTTP GET (no auth needed). The response is image/jpeg.

3

Store the image in your own CDN, S3, or media library — do not rely on SEOcraftAI as permanent image hosting.

Node.js example:js
if (payload.article.main_image_url) {
  const res = await fetch(payload.article.main_image_url);
  const buffer = Buffer.from(await res.arrayBuffer());
  // upload buffer to S3, Cloudinary, WordPress media, etc.
  // then store the new URL in your CMS — don't link directly to SEOcraftAI
}
PHP example:php
if (!empty($payload['article']['main_image_url'])) {
  $img = file_get_contents($payload['article']['main_image_url']);
  // file_put_contents('/uploads/cover.jpg', $img);
}

Handling updates

1

The article.id UUID is stable across republications — it never changes for the same article.

2

Store the UUID in your database. On each incoming webhook, check if a record with that UUID already exists.

*

If found: update the existing record. If not found: insert a new one. This prevents duplicate posts when an article is edited and republished.

Upsert pattern (Node.js / SQL):js
const existing = await db.query(
  'SELECT id FROM posts WHERE SEOcraftAI_id = $1',
  [payload.article.id]
);
if (existing.rows.length > 0) {
  await db.query('UPDATE posts SET title=$1, content=$2 WHERE SEOcraftAI_id=$3',
    [payload.article.title, payload.article.content_markdown, payload.article.id]);
} else {
  await db.query('INSERT INTO posts (SEOcraftAI_id, title, content) VALUES ($1,$2,$3)',
    [payload.article.id, payload.article.title, payload.article.content_markdown]);
}

Response requirements

!

Your endpoint must return a 2xx status code (200-299) within 30 seconds. Any other response is treated as a failure.

1

Return the response immediately and process heavy work (image upload, rendering) asynchronously to stay within the time limit.

2

A minimal success response: HTTP 200 with any body, e.g. { "ok": true }.

Publishing articles

1

Open Integrations -> Custom Webhook from the sidebar.

2

Click Publish Article to expand the publish form.

3

Enter the article title and paste the Markdown content.

4

Click Send via Webhook — SEOcraftAI POSTs the full payload (title, content_markdown, content_html, SEO fields, cover image URL) to your endpoint.

5

The response status appears inline — green means your endpoint accepted it.

Common issues

1

Endpoint not reachable — verify your URL is publicly accessible over HTTPS with a valid SSL certificate. Test it with curl from another machine.

2

Auth failures — check for trailing whitespace in your token. Header names are case-sensitive on some servers.

3

Timeout errors — your endpoint must respond within 30 seconds. Move image processing, database writes, and rendering to a background job.

4

Image not showingmain_image_url points to SEOcraftAI's server. Fetch and re-host the image in your own storage — do not embed the URL directly in your CMS.

5

Duplicate posts — store article.id and use it to upsert instead of always inserting.

Notes

Webhook credentials are saved with your account — you only need to configure them once.

One webhook endpoint per site is supported. Contact support if you need multiple endpoints.