SearchChampSearchChamp
Integrations

Connect a custom webhook

Publish Content Writer articles to your own HTTPS receiver using signed, versioned, idempotent events.

Custom Webhook lets an eligible SearchChamp workspace publish Content Writer articles through an HTTPS endpoint it controls. It is a delivery integration, not a second editor: create and revise the article in Content Writer, then choose the connected webhook as the site's publish destination.

Limited rollout

Custom Webhook is not generally available. The card and publish destination appear only for workspaces included in the current rollout. Contact SearchChamp support if you need access.

Prerequisites and connection steps
  • An editor-or-higher SearchChamp role on the site.
  • One public https:// endpoint on port 443 that accepts POST requests within 15 seconds.
  • A receiver that preserves the raw request bytes, verifies HMAC-SHA256, rejects stale deliveries, and stores idempotency keys.

Open the integration

Go to Site Settings → Integrations, find Custom webhook, and select Connect.

Enter the endpoints

Enter the publish URL. You may enter a separate revert URL; otherwise reverts use the publish URL. SearchChamp rejects HTTP, redirects, credentials in URLs, unsafe/private network destinations, and SearchChamp-owned infrastructure.

Provision the signing secret

SearchChamp generates and stores the connection secret securely. The application never returns or displays its value. Your receiver operator must obtain the corresponding secret through the approved secure provisioning process; never put it in a URL, source repository, browser code, log, ticket, or article.

Test readiness

Select Test connection. SearchChamp sends cw.test_fire, signed exactly like live events. It contains no article body and must cause no article or media write. Return 2xx only when the receiver is reachable and ready.

Headers and signature verification

Every request has:

HeaderValue
Content-Typeapplication/json
X-Atlio-Signaturesha256= followed by the lowercase hexadecimal HMAC
X-Atlio-Issued-AtThe same ISO-8601 timestamp carried by issued_at
X-Atlio-Idempotency-KeyThe same key carried by idempotency_key

Compute HMAC-SHA256 over the exact bytes received, using the connection secret. Compare the expected and received digests with a constant-time primitive. Authenticate before parsing, logging, fetching media, or writing data. Reject an invalid timestamp, a delivery outside your replay window, a header/body mismatch, or an already-used key with different bytes.

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(rawBody, signatureHeader, secret) {
  const receivedHex = signatureHeader?.match(/^sha256=([0-9a-f]{64})$/)?.[1];
  if (!receivedHex) return false;
  const expected = createHmac('sha256', secret).update(rawBody).digest();
  const received = Buffer.from(receivedHex, 'hex');
  return received.length === expected.length && timingSafeEqual(received, expected);
}

Do not parse and re-serialize before verification: whitespace and key order change the signed bytes.

Envelope and events

All events use envelope version 1 and share these fields:

FieldMeaning
eventcw.test_fire, cw.publish, or cw.revert
envelope_versionBreaking-contract version; currently 1
idempotency_keyDelivery identity, also sent in the header
issued_atSigning timestamp, also sent in the header
workspace_id, site_idSearchChamp source identity

Receivers must ignore unknown additive fields. Reject an unsupported envelope_version or unknown event; a new field alone does not increment the version.

cw.test_fire also carries a human-readable message and no article payload.

cw.publish carries article_id plus payload, including source article/revision identity, title, fixed slug, status, Markdown/body content, SEO metadata, target keyword, language/direction, tags, image descriptors, and — for enabled workspaces — a resolved payload.author object alongside the schema graph. Treat all author-authored strings and structured data as untrusted input.

When present, payload.author describes the person SearchChamp attributed the article to. Its current wire shape carries these keys; receivers should continue to ignore unknown additive fields:

keytypenotes
namestringRequired. 1 to 160 characters. This is the byline name.
credentialsstring, or absentUp to 200 characters. A job title or qualification.
biostring, or absentUp to 600 characters.
photo_urlstring, or absentAn absolute http(s) URL, up to 2,048 characters.
sameasarray of strings, or absentUp to 20 absolute http(s) profile URLs, each up to 2,048 characters. These supply Person.sameAs in the schema graph.
profile_urlstring, or absentThe author's own profile page on their site; an absolute http(s) URL up to 2,048 characters.

Three rules matter when you parse it.

  1. Optional keys are absent rather than null. This applies to credentials, bio, photo_url, sameas, and profile_url. An omitted bio means there is no bio, not an empty one.
  2. The whole object is absent when the workspace is outside the author-metadata rollout, there is no effective author (neither an article assignment nor a site default), or that author has not consented to appear publicly. The article still publishes in all three cases. Apply your own fallback byline rather than blocking the publish.
  3. sameas supplies social profiles. The schema graph's Person.url uses profile_url when present; otherwise it uses the first valid sameas URL, then the site's origin. If sameas carries no LinkedIn URL, render no LinkedIn link rather than an empty one.

cw.revert identifies the source article, the receiver's prior cms_post_id, and the revert action. A revert means hide/tombstone reversibly; it does not require destructive deletion.

Images and placeholder substitution

Image descriptors include a role, source URL, MIME/size metadata where available, and a placeholder token present in the article body. Before making an article visible:

  1. Fetch through an SSRF-safe client with DNS/IP checks, no redirects, strict timeouts, image-count and byte limits.
  2. Verify the actual bytes and MIME type; do not trust the extension or header alone.
  3. Copy the image into storage you own under an immutable revision-specific key.
  4. Replace every placeholder with your owned public URL, then sanitize the final HTML.

Never publish a placeholder token, source storage key, credential, private URL, or presigned URL. Define whether a failed hero rejects the publish and whether a failed inline image is removed with a warning; make the policy deterministic and idempotent.

Acknowledgements, identity, and lifecycle

Return 2xx only after the durable publish/update/revert commit completes. A publish acknowledgement should be JSON:

{
  "cms_post_id": "stable-receiver-post-id",
  "published_url": "https://example.com/blog/fixed-slug",
  "receiver_revision_id": "receiver-revision-id",
  "warnings": []
}

cms_post_id must stay stable across republishing. The slug and canonical URL stay fixed; republishing adds revision history. Store workspace, site, source article, source revision token, idempotency key, receiver post/revision IDs, canonical URL, content hash, lifecycle timestamps, verified envelope, and original acknowledgement so the complete identity chain remains joinable.

Store the exact acknowledgement bytes with the idempotency record. An identical retry must return those bytes without duplicating content or media. The same key with different request bytes is a conflict. A revert is idempotent and reversible; later publish may restore visibility as a new revision.

Status, errors, and retries
  • 2xx: the receiver completed the event durably. Do not acknowledge queueing alone unless your durable queue is the contract boundary and produces the required stable identity.
  • 4xx: authentication, stale timestamp, unsupported contract, validation, conflict, or policy rejection. Use a stable safe error code.
  • 5xx or timeout: transient receiver failure. Do not claim success.
  • 3xx: redirects are blocked.

SearchChamp records a non-2xx or transport failure as a failed delivery. Design your receiver for redelivery even if a particular sender path does not automatically retry: network failures can occur after your commit but before SearchChamp receives the response.

Security checklist
  • Verify TLS and raw-body HMAC before parsing or logging.
  • Enforce a short issued-at window and durable idempotency uniqueness.
  • Validate tenant/source identity, field lengths, slug/path rules, event/version, body size, and media limits.
  • Sanitize HTML with an allowlist. Never evaluate incoming MDX, JSX, imports, expressions, scripts, event handlers, or unsafe URLs.
  • Keep signing secrets in a secret manager and rotate after suspected exposure.
  • Use private encrypted media storage, blocked public access, and least-privilege delivery access.
  • Log only redacted event IDs/outcomes; never signatures, secrets, article bodies, presigned URLs, or credentials.
  • Test racing duplicates and transaction failures, not only sequential happy paths.
Testing and troubleshooting

Test valid publish, bad signature, changed raw bytes, stale timestamp, replay/racing duplicate, unknown additive fields, unsupported version/event, invalid slug, large/invalid/partial media, republish, slug refusal, repeated revert, and failure after commit-before-response.

  • Test connection says blocked: confirm public HTTPS:443, DNS resolution, certificate validity, and that the endpoint is not private/SearchChamp infrastructure.
  • Signature mismatch: capture raw bytes before any JSON middleware; confirm the correct connection secret and sha256= format.
  • Stale request: compare UTC clocks and your replay window.
  • Duplicate content: enforce a database uniqueness constraint and return the stored acknowledgement.
  • Tokens appear in the article: process every image descriptor before final sanitization and scan the rendered output.
  • SearchChamp shows failure but the page exists: the response was lost or late; replay the same key and return the original acknowledgement.

On this page