Webhook

We POST each article's full payload to a URL you choose the moment it's published — no polling needed.

Setup

From Integrations → Webhook, enter your endpoint URL. We'll generate a signing secret — store it, since it's shown only once.

Payload

POST to your URL with a JSON body shaped like the API's single-article response, plus coverImageUrl:

{
  "id": "...",
  "slug": "best-seo-tools-2026",
  "title": "Best SEO Tools 2026",
  "metaTitle": "Best SEO Tools 2026",
  "metaDescription": "...",
  "html": "<h1>...</h1>...",
  "markdown": "# ...",
  "angle": "listicle",
  "language": "en-US",
  "publishedAt": "2026-08-01",
  "coverImageUrl": "https://.../article-images/<business>/<article>.png",
  "jsonLd": { "@type": "Article", "...": "..." }
}

coverImageUrl is a publicly-fetchable URL for the article's generated cover image, or null if it hasn't generated yet. Any inline images an article contains are already in html/markdown as regular <img> tags, however many there are — there's no separate field per image.

Compatibility: webhook payloads may gain new optional fields over time. Your webhook handler should ignore fields it does not recognize. Existing fields will remain backwards compatible unless a future breaking contract is explicitly introduced (which would be announced and versioned separately).

Verifying the signature

Every request includes an X-HoneyTraffic-Signature header: sha256=<hex>, an HMAC-SHA256 of the raw request body using your signing secret. Recompute it and compare:

import { createHmac, timingSafeEqual } from "crypto";

function isValidSignature(rawBody, header, secret) {
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  return timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}

Delivery behavior

  • We expect a 2xx response within 5 seconds.
  • Failed deliveries are retried automatically with backoff.
  • Use the Send a test event step in the connect wizard to verify your endpoint before relying on it.