Custom-site webhook integration
Connect any site that isn't WordPress/Webflow/Shopify via a signed webhook.
Last updated May 27, 2026
For any tenant whose site isn't WordPress, Webflow, or Shopify — static sites (Hugo, Jekyll, Astro, Next.js), Wix/Squarespace with a custom function, headless CMSes, in-house CMS, or anything that can expose an HTTP endpoint — connect via a signed webhook.
How it works
- You build a tiny endpoint on your site that accepts
POSTwithContent-Type: application/json. - You verify the
X-AID-Signatureheader (HMAC-SHA256, Stripe-compatible format). - Your endpoint persists the content into your CMS / Git repo / blog database, and returns 2xx.
- (Optional) Include
{ "url": "..." }in the response body to land the published URL on the ContentItem.
We POST one payload per publish. Same payload shape for every content type (Article, FAQ, Comparison, Location Page, Service Page).
The payload
{
"v": "1",
"action": "publish",
"timestamp": "2026-05-27T03:14:00.000Z",
"contentItemId": "ckxyz...",
"content": {
"type": "ARTICLE",
"title": "How to choose a tax-resolution firm",
"slug": "how-to-choose-a-tax-resolution-firm",
"bodyMarkdown": "## The short answer...",
"metaTitle": "How to choose a tax-resolution firm (2026)",
"metaDescription": "A practical 5-minute guide...",
"schemaJsonLd": { "@context": "https://schema.org", "@type": "Article", ... },
"targetPrompts": ["best tax resolution firm", "how to pick a tax-resolution attorney"],
"cta": "Schedule a free consult."
}
}
Signature verification
X-AID-Signature header looks like:
X-AID-Signature: t=1716774840,v1=8f2a5b...64a3e1
To verify in your receiver:
- Parse
t(unix seconds) andv1(hex). - Reject if
abs(now - t) > 300seconds (replay protection). - Recompute
hmac_sha256(${t} + "." + rawRequestBody, secret). - Compare with
v1using a timing-safe equal.
Node.js reference receiver
import { createHmac, timingSafeEqual } from "node:crypto";
const SECRET = process.env.AID_WEBHOOK_SECRET;
export async function POST(req) {
const raw = await req.text();
const sig = Object.fromEntries(
(req.headers.get("x-aid-signature") ?? "")
.split(",")
.map((p) => p.split("=").map((s) => s.trim()))
);
const t = Number(sig.t);
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) {
return new Response("stale", { status: 400 });
}
const expected = createHmac("sha256", SECRET).update(`${t}.${raw}`).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(sig.v1 ?? "", "hex");
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return new Response("bad signature", { status: 401 });
}
const payload = JSON.parse(raw);
if (payload.action === "test") {
return Response.json({ ok: true });
}
// Persist content however your CMS / static site / Git repo wants it.
// Return { url: "..." } so it lands on the ContentItem.
const url = await persistToYourCms(payload.content);
return Response.json({ url });
}
Python (FastAPI) reference receiver
import hashlib, hmac, time, os
from fastapi import FastAPI, Header, HTTPException, Request
SECRET = os.environ["AID_WEBHOOK_SECRET"].encode()
app = FastAPI()
@app.post("/webhook")
async def webhook(req: Request, x_aid_signature: str = Header(...)):
raw = (await req.body()).decode()
parts = dict(p.strip().split("=", 1) for p in x_aid_signature.split(","))
t = int(parts["t"])
if abs(time.time() - t) > 300:
raise HTTPException(400, "stale")
expected = hmac.new(SECRET, f"{t}.{raw}".encode(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, parts.get("v1", "")):
raise HTTPException(401, "bad signature")
payload = await req.json()
if payload.get("action") == "test":
return {"ok": True}
url = persist_to_your_cms(payload["content"])
return {"url": url}
GitHub Actions reference (for static sites)
If your site is built from a Git repo (Hugo, Jekyll, Astro, Next.js static), use a tiny serverless function as the webhook receiver. It verifies the signature, writes a Markdown file to your repo, and triggers a deploy.
The pattern is identical to the Node.js example above; the persistence step is git commit + git push (via the GitHub REST API, the Octokit SDK, or your platform's "trigger build" webhook).
Configuration
In the AI Domination dashboard:
- Open a company → Publishing → Custom (webhook) tab.
- Paste your webhook URL and a 32+ character signing secret.
- Click Test & connect. We POST a
content.testevent; we only save the configuration if your receiver replies 2xx with a valid signature.
Generate a secret with openssl rand -hex 32 and paste the same value into your receiver's environment.
What we send
Every publish triggers exactly one POST with action: "publish". We don't retry on 5xx (you'll see the failure in the ContentItem's status — re-publish from the dashboard). 4xx is treated as a permanent failure.
What we expect back
Any 2xx status code = success. Optional JSON body with the published URL helps but isn't required. We tolerate text/plain responses (just say "ok") for receivers that don't want to round-trip JSON.
See also
Was this article helpful?