How to connect Zapier, n8n, Make, or any webhook
Nuwtonic pushes published articles and supported events to a Zapier, n8n, Make, or custom HTTPS webhook URL that you provide. Nuwtonic does not charge for webhook delivery. If you use an automation provider, you pay that provider.
Use this when you want to send articles or optimization instructions to WordPress, Slack, Sheets, a CRM, a custom CMS, or another system.
Important: a webhook delivers instructions; it does not update your CMS by itself. Your Zap, n8n workflow, Make scenario, or custom receiver must find the target page and perform the requested update. If you want Nuwtonic to update WordPress without building that workflow, use the native WordPress integration.
For complete node-by-node examples for article publishing and content fixes, open Zapier, n8n, Make, and webhook examples.
1. Choose what the workflow will do
One webhook URL receives all Nuwtonic events. Route each request using the top-level event field:
- Publish an article: process
content.generatedonly whendata.resultcontainstitle,content, orcontent_html. - Apply content and SEO fixes: process only
fix.apply. Find the existing page usingdata.target_url, then apply the instructions indata.fixes. - Ignore or log publish completion: a
content.generatedpayload containingdata.result.published_countis a summary, not another article. - Ignore or log fix completion:
fix.generatedreports the final job status. It does not contain new fixes to apply. - Acknowledge tests:
testonly confirms that Nuwtonic can reach the URL.
Do not send every event to the same CMS action. That can create duplicate posts or write a completion payload into an article.
2. Paste your URL in Nuwtonic
- Open the workspace you want to connect.
- Open Integrations.
- Find Zapier, n8n, Make, or any webhook and select Connect.
- Paste the HTTPS URL from Zapier, n8n, Make, or your custom webhook endpoint.
- Turn Enable webhook on and save.
- Copy the signing secret if your receiver can verify webhook signatures.
- Select Send Test Event and confirm that your automation receives
event: test. - Test an article publish and a fix separately before enabling the workflow for production.
If the URL is missing, the toggle is off, or your endpoint is not HTTPS, Nuwtonic cannot send articles or fixes.
Trigger real article and fix samples
Send Test Event sends only event: test. It checks the connection but does not include an article or any fixes.
To test publishing:
- Create or open a generated article in Nuwtonic.
- Choose the webhook integration and publish a low-risk test article.
- Confirm that the automation receives an actionable
content.generatedevent.
To test content and SEO fixes:
- Open SEO Performance → SEO Audit & Fix.
- Run an audit for a low-risk page that you can restore if needed.
- Review the generated changes.
- Select Apply Fix to CMS for one page. Use Apply All Fixes only after the single-page workflow succeeds.
- Confirm that the automation receives
event: fix.apply, finds the same page fromdata.target_url, and appliesdata.fixes. - Check the live page and CMS fields before using the workflow on more pages.
The webhook must be the enabled integration for that workspace when you publish or apply the fix.
3. Zapier setup
- In Zapier, create a Zap and choose Webhooks by Zapier → Catch Hook. This trigger needs a paid Zapier plan.
- Copy the Catch Hook URL.
- Paste that URL into Nuwtonic, enable the webhook, and save.
- Select Send Test Event in Nuwtonic and load that request in Zapier.
- Add Paths by Zapier inside this Zap:
- Article path: continue when
eventequalscontent.generated,data.result.titleor body exists, anddata.result.published_countdoes not exist. - Fix path: continue when
eventequalsfix.apply. - Other events: stop, or send them to a log.
- Article path: continue when
- Configure the article and fix actions described below.
- Test each path, then turn the Zap on.
You pay Zapier, not Nuwtonic.
Zapier article path
Map these fields to Create Post, Update Post, or another destination action:
- title:
data.result.title - body:
data.result.content_htmlordata.result.content - slug:
data.result.slug - status:
data.result.status - SEO description, if supported:
data.result.meta_description
If the destination requires an existing record ID, add a lookup step before Update Post.
Zapier fix path
- Read
data.target_urland find exactly one matching CMS page or post. - Use Looping by Zapier, Code by Zapier, or a custom API request to process
data.fixes. - Route each fix by its
type; do not paste everysuggested_valueinto the article body. - Save the updated page only after the target and current content have been validated.
- Log failures in Zap History and alert the user.
Zapier's standard WordPress actions may handle basic title and body updates but may not expose SEO-plugin metadata, JSON-LD, image alt text, or exact section replacement. For those fixes, call an authenticated CMS endpoint with Webhooks by Zapier, add a small custom receiver, or use Nuwtonic's native WordPress integration.
Zapier Catch Hook acknowledges the request before later actions finish. A successful webhook receipt therefore does not prove that every downstream Zapier action succeeded. Monitor Zap History and configure error alerts.
4. n8n setup
- Add a Webhook node with method POST.
- Select its Production URL. Do not use the temporary Test URL for the permanent Nuwtonic connection.
- Set the Webhook node to respond using Respond to Webhook.
- Activate the workflow, copy the Production URL into Nuwtonic, enable the webhook, and save.
- Add a Switch node using the
eventfield:content.generated→ check for a real article and reject summaries containingpublished_count.fix.apply→ run the fix workflow.test,fix.generated, and summaries → acknowledge and stop or log.
- Configure the article and fix branches described below.
- Add Respond to Webhook after the destination action. Return
{"id":"<destination-id>","link":"<final-url>"}after a successful article publish or fix application. - Test both branches while the workflow is active.
You pay n8n (or your n8n host), not Nuwtonic.
n8n article branch
Use a WordPress, HTTP Request, database, or CMS node. Map the article fields from data.result. Return the destination record ID and final URL with Respond to Webhook.
n8n fix branch
- Resolve
data.target_urlto one editable CMS record. - Use a Code, Split Out, or loop node to inspect
data.fixes. - Apply the fix rules in How to apply
fix.applybelow. - Prefer loading the page once, applying all safe fixes, and saving once.
- Return
404if no page matches,409ifcurrent_valueno longer matches, or422if your CMS cannot support a fix type. - Return the destination ID and URL only after the update succeeds.
5. Make setup
- In Make, create a scenario and add Webhooks → Custom webhook.
- Copy its URL into Nuwtonic, enable the webhook, and save.
- Select Run once in Make, then send a Nuwtonic test event so Make detects the payload.
- Add a Router with filters:
- Article route:
event = content.generated, an article title or body exists, andpublished_countdoes not exist. - Fix route:
event = fix.apply. - Notification route: log or ignore
test,fix.generated, and publish summaries.
- Article route:
- Configure the article and fix routes described below.
- Put Webhook Response after the destination update. On success, return status
200and{"id":"<destination-id>","link":"<final-url>"}. - Test both routes, choose an appropriate schedule, and activate the scenario.
You pay Make, not Nuwtonic.
Make article route
Map data.result.title, content_html or content, slug, and status to a WordPress, HTTP, database, or CMS module.
Make fix route
- Find the page using
data.target_url. - Use an Iterator for
data.fixes, or use a code/API module that applies all fixes in one operation. - Route by fix
typeand follow the safe-apply rules below. - Aggregate the changes and save once where possible.
- Use Webhook Response only after the destination confirms the update.
6. How to apply fix.apply
The fix.apply event contains instructions for an existing page:
data.target_url: the exact page Nuwtonic audited.data.fixes: the list of requested changes.- each fix has a
type,location, andsuggested_value;actionmay bebefore,after, orreplaceand defaults toafter. Some fixes also includecurrent_value,location_heading, orreasoning.
Your automation must handle each type correctly:
meta: update the SEO title or meta-description field. Do not insert the<title>or<meta>markup into the visible article body.schema: parse and validatesuggested_valueas JSON-LD, then update the CMS schema/head field. Do not show JSON-LD as visible text.content_addition: add the supplied HTML before, after, or inside the named location. Check that equivalent content is not already present.content_replacement: replace only the suppliedcurrent_valueat the identified location. If it no longer matches, stop and report a conflict instead of replacing similar text.structural: update the identified heading or section only when the target is unambiguous.image_alt: find the image using the URL inlocationand update only its alt text.internal_link: add or update the uniquely identified link without replacing unrelated text.faq: add visible FAQ content. Some requests send FAQ as two fixes:content_additionfor visible HTML andschemafor FAQPage JSON-LD; apply both without duplicating either.
If a destination cannot support a fix type, stop that fix and record a clear error. Do not silently mark it applied.
Destination requirements
- WordPress: the workflow needs permission to find and update the post/page. SEO title, meta description, and schema often belong to Yoast, Rank Math, another plugin, or custom fields that must be exposed through the WordPress REST API. The standard WordPress automation action may not expose them.
- Shopify, Webflow, Ghost, or another CMS: use the platform's update API and credentials. Confirm which fields accept metadata, JSON-LD, HTML, and image alt text.
- Slack, Sheets, email, or a CRM: these can log fixes or request approval, but they do not apply a website fix unless another step updates the CMS.
Required success and failure responses
For an applied article or fix, n8n, Make, and custom receivers should return a 2xx response with the destination ID and final URL:
{
"id": "cms-post-123",
"link": "https://example.com/final-page"
}
Return a non-2xx response when the operation was not safely applied. A 2xx response means success to Nuwtonic.
Zapier Catch Hook returns its own acknowledgment before the Zap finishes, so it cannot confirm the final CMS result in this response. With Zapier, Nuwtonic can confirm delivery to Zapier but not the success of later actions. Use Zap History and error alerts.
7. Prevent duplicates and unnecessary automation charges
A publish job can send the article and a completion summary to the same URL. A fix job can send fix.apply and a later fix.generated notification. Only the first event in each pair is actionable.
- Publish only when
event = content.generated, an article title or body exists, anddata.result.published_countdoes not exist. - Apply fixes only when
event = fix.applyand bothdata.target_urlanddata.fixesexist. - Never apply changes for
fix.generated. - Store each top-level event
idand do not process it twice.
8. Article payload example
Map these fields in Zapier, n8n, or Make:
{
"id": "evt_0f1d2c3b4a5e6f70",
"event": "content.generated",
"created": 1785058200,
"data": {
"job_id": "content-job-001",
"workspace_id": "workspace-id",
"status": "SUCCESS",
"result": {
"id": "generated-article-id",
"keyword": "best ai seo tools",
"title": "Best AI SEO Tools",
"slug": "best-ai-seo-tools",
"status": "publish",
"content": "<h1>Best AI SEO Tools</h1><p>...</p>",
"content_html": "<h1>Best AI SEO Tools</h1><p>...</p>",
"meta_description": "A clear description for search results."
}
}
}
Use data.result.title and data.result.content or data.result.content_html as the article. Ignore a later payload that has data.result.published_count. Automation field pickers may display these as labels such as Data → Result → Title instead of dot notation.
9. Fix payload example
This example asks the workflow to update one existing article. It must not create a new post:
{
"id": "evt_214a2f9bdc294df1",
"event": "fix.apply",
"created": 1785059000,
"data": {
"target_url": "https://example.com/blog/best-ai-seo-tools",
"fixes": [
{
"type": "meta",
"location": "<head>",
"action": "replace",
"suggested_value": "<title>Best AI SEO Tools for 2026</title>"
},
{
"type": "content_replacement",
"location": "Introduction",
"action": "replace",
"current_value": "Old introduction text.",
"suggested_value": "Updated introduction text."
}
]
}
}
The workflow must find the existing page from target_url, validate current_value, update the SEO title in the metadata field, replace the exact introduction text, save the page, and return its ID and URL.
10. Security and authentication
Incoming Nuwtonic webhook: Nuwtonic sends a signed JSON POST. Your custom receiver should verify X-ContentKit-Signature with the signing secret. Managed Catch Hook / Custom Webhook triggers can receive the request without adding an API key, but you should keep their generated URLs private.
Calls from your automation to your CMS: use the authentication required by that CMS. Never place a CMS password or token in a Nuwtonic webhook URL.
Calls from an HTTP module to the Nuwtonic REST API: use Authorization: Bearer <api_key>. Do not use X-API-Key.
Developer signature details are in the technical guide below.
Webhook Integration Guide
Use the webhook integration when you want Nuwtonic to send generated content and optimization instructions to your own CMS, backend, or automation.
You configure one webhook URL. The same URL receives:
- generated articles that your system must publish;
- optimization fixes that your system must apply;
- completion notifications describing the final result.
Your endpoint must inspect the top-level event field and the contents of data.result before deciding what to do.
1. Configuration
Configure the integration with this credential structure:
{
"webhook": {
"url": "https://cms.example.com/webhooks/nuwtonic",
"secret": "generated-by-nuwtonic",
"enabled": true
}
}
Configuration fields:
urlis the public HTTPS endpoint that receives every webhook event.secretis generated by Nuwtonic and signs each request with HMAC-SHA256. Copy it when you create or rotate it if your receiver verifies signatures.enabledcontrols delivery. Whenfalse, publishing and fix application are skipped.
Use the nested webhook object shown above for all webhook integrations.
Your endpoint must:
- Accept
POSTrequests with JSON bodies. - Read and preserve the raw request body before parsing JSON.
- Verify the signature.
- Route the request using
event. - Make processing idempotent using the top-level event
id. - Return a
2xxresponse within 30 seconds.
2. Common request envelope
Every request uses this structure:
{
"id": "evt_0f1d2c3b4a5e6f70",
"event": "content.generated",
"created": 1785058200,
"data": {}
}
Common fields:
id: unique event identifier. Store it and do not process the same ID twice.event: event type used to route the request.created: Unix timestamp in seconds.data: event-specific payload.
The two actionable event types are:
content.generated: publish the article only whendata.resultis an article object.fix.apply: apply the supplied fixes todata.target_url.
Completion notifications are:
content.generated: publish-job summary whendata.result.published_countexists.fix.generated: fix-job completion status.
3. Headers and signature verification
Signed requests include:
Content-Type: application/json
X-ContentKit-Signature: t=1785058200,v1=<hex-hmac-sha256>
X-ContentKit-Timestamp: 1785058200
Signature algorithm
Build the signed value from the timestamp header, a period, and the exact raw request body:
signed_payload = X-ContentKit-Timestamp + "." + raw_request_body
signature = HMAC_SHA256(webhook_secret, signed_payload)
Compare the hexadecimal result with the v1 value from X-ContentKit-Signature using a constant-time comparison.
Do not parse and re-serialize the JSON before verification. Whitespace and key order are part of the signature.
Recommended checks:
- reject requests with missing signature headers;
- reject invalid signatures with
401; - reject timestamps older than five minutes to reduce replay risk;
- store processed event IDs and return
2xxfor already processed IDs.
Python verification example
import hashlib
import hmac
import time
def verify_webhook(raw_body: bytes, signature_header: str, timestamp: str, secret: str) -> bool:
if not signature_header or not timestamp:
return False
parts = dict(
part.split("=", 1)
for part in signature_header.split(",")
if "=" in part
)
if parts.get("t") != timestamp or not parts.get("v1"):
return False
try:
timestamp_value = int(timestamp)
except ValueError:
return False
if abs(int(time.time()) - timestamp_value) > 300:
return False
signed_payload = timestamp.encode() + b"." + raw_body
expected = hmac.new(
secret.encode(),
signed_payload,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
Node.js verification example
import crypto from "node:crypto";
export function verifyWebhook(rawBody, signatureHeader, timestamp, secret) {
if (!signatureHeader || !timestamp) return false;
const parts = Object.fromEntries(
signatureHeader
.split(",")
.map((part) => part.split("=", 2))
.filter(([key, value]) => key && value)
);
if (parts.t !== timestamp || !parts.v1) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > 300) {
return false;
}
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const expectedBuffer = Buffer.from(expected, "hex");
const receivedBuffer = Buffer.from(parts.v1, "hex");
return (
expectedBuffer.length === receivedBuffer.length &&
crypto.timingSafeEqual(expectedBuffer, receivedBuffer)
);
}
4. Generated article event
When a user publishes or saves generated content through the webhook integration, your endpoint receives one actionable content.generated event per article.
{
"id": "evt_0f1d2c3b4a5e6f70",
"event": "content.generated",
"created": 1785058200,
"data": {
"job_id": "content-job-001",
"workspace_id": "workspace-id",
"domain": "example.com",
"status": "SUCCESS",
"summary": {
"total": 1,
"processed": 1,
"failed": 0
},
"result": {
"id": "generated-article-id",
"keyword": "best ai seo tools",
"title": "Best AI SEO Tools",
"slug": "best-ai-seo-tools",
"status": "publish",
"content": "<h1>Best AI SEO Tools</h1><p>...</p>",
"content_html": "<h1>Best AI SEO Tools</h1><p>...</p>",
"meta_description": "A clear description for search results.",
"category": "SEO",
"categories": ["SEO"],
"schema": {
"@context": "https://schema.org",
"@type": "Article"
}
}
}
}
data.result is the generated article record. It is passed through from Nuwtonic, so optional fields can vary by generation workflow. Do not reject the complete event because an optional example field is absent.
At minimum, your publisher should:
- Validate the fields required by your CMS.
- Use
result.idor the event ID as an idempotency key. - Create or update the article.
- Respect
result.status:publish: make the article live;draft: save it without making it live.
- Store and return the final CMS ID and article URL.
If slug is missing, Nuwtonic may derive it from keyword, but your system should still validate it before publishing.
Required response
Return a JSON response with the external resource ID and final article URL:
{
"id": "cms-post-123",
"link": "https://cms.example.com/blog/best-ai-seo-tools"
}
url is accepted instead of link:
{
"id": "cms-post-123",
"url": "https://cms.example.com/blog/best-ai-seo-tools"
}
Always return the real article URL. Responses without link or url cannot reliably identify the published article.
5. Publish completion notification
After all requested articles have been processed, the same URL can receive another content.generated event. This second event is a job summary and must not create another article.
Identify it by the presence of data.result.published_count and data.result.results.
{
"id": "evt_91ba9852ea9146ab",
"event": "content.generated",
"created": 1785058260,
"data": {
"job_id": "publish-job-123",
"workspace_id": "workspace-id",
"domain": "example.com",
"status": "SUCCESS",
"summary": {
"total": 1,
"processed": 1,
"failed": 0
},
"result": {
"published_count": 1,
"results": [
{
"keyword": "best ai seo tools",
"status": "published",
"post_id": "cms-post-123",
"link": "https://cms.example.com/blog/best-ai-seo-tools",
"category": "SEO",
"categories": ["SEO"]
}
],
"selected_category": "SEO",
"selected_categories": ["SEO"]
}
}
}
For this summary event:
- record or display the job result if needed;
- do not publish
data.resultas an article; - return any
2xxresponse.
6. Optimization fix event
When optimizations are pushed through the webhook integration, your endpoint receives:
{
"id": "evt_214a2f9bdc294df1",
"event": "fix.apply",
"created": 1785059000,
"data": {
"target_url": "https://cms.example.com/blog/best-ai-seo-tools",
"summary": {
"total": 4,
"processed": 4,
"failed": 0
},
"fixes": [
{
"type": "meta",
"location": "<head>",
"action": "replace",
"suggested_value": "<title>Best AI SEO Tools for 2026</title>"
},
{
"type": "content_replacement",
"location": "Introduction",
"location_heading": "H2",
"action": "replace",
"current_value": "Old introduction text.",
"suggested_value": "Updated introduction text.",
"reasoning": "The updated introduction answers the search intent directly."
},
{
"type": "image_alt",
"location": "https://cdn.example.com/images/seo-dashboard.jpg",
"action": "replace",
"current_value": "",
"suggested_value": "SEO analytics dashboard showing keyword growth"
},
{
"type": "schema",
"location": "<head>",
"action": "after",
"suggested_value": "{\"@context\":\"https://schema.org\",\"@type\":\"Article\"}"
}
]
}
}
Use data.target_url to find the existing page or article. Do not apply a fix to another page when the URL cannot be resolved uniquely.
Fix object fields
Each item in data.fixes contains:
type: required fix type.location: required target or placement instruction.action:before,after, orreplace. If omitted, treat it asafter.suggested_value: required new value.current_value: optional value observed during the audit.location_heading: optional heading or element hint.reasoning: optional explanation for the recommendation.
Optional values are omitted from the JSON rather than sent as null.
Fix types
meta
Updates SEO metadata. suggested_value normally contains either:
<title>Updated SEO title</title>
or:
<meta name="description" content="Updated search description">
Update SEO metadata rather than renaming the visible article unless your CMS intentionally uses the same field for both.
schema
Adds or updates JSON-LD. suggested_value contains a serialized JSON object.
Validate it with a JSON parser before saving it. Render it as:
<script type="application/ld+json">...</script>
Do not insert an equivalent schema block twice.
content_addition
Adds HTML content relative to location. Common placements include end of content or a named heading.
Before adding it, check whether equivalent content already exists.
content_replacement
Replaces existing content. When current_value is present, only replace content that still matches it. If it has changed since the audit, skip the fix and report the conflict.
structural
Changes document structure, such as headings or sections. Use location, location_heading, and current_value together. Skip ambiguous targets.
image_alt
Updates image alt text:
locationis the image URL;current_valueis the previous alt text when available;suggested_valueis the new alt text.
Match the image uniquely. Preserve the image itself, file ID, dimensions, caption, and URL.
internal_link
Adds or updates an internal link. Preserve the surrounding text and only change the uniquely identified anchor.
faq
Adds FAQ content. Manual fix requests may use type: "faq".
High-level SEO requests usually produce two separate fixes instead:
content_additioncontaining visible FAQ HTML;schemacontainingFAQPageJSON-LD.
Support both representations.
Applying fixes safely
Recommended processing order:
- Resolve
target_urlto exactly one editable resource. - Load the latest version from your CMS.
- Verify every supplied
current_value. - Apply metadata and schema.
- Apply image alt fixes.
- Apply additions and links.
- Apply replacements and structural changes.
- Save using optimistic locking or version checks when available.
- Publish only after the complete update succeeds.
Skip a fix instead of guessing when its target is missing or ambiguous.
Required response
After applying the fixes, return:
{
"id": "fix-operation-456",
"link": "https://cms.example.com/blog/best-ai-seo-tools"
}
url is accepted instead of link.
Return 2xx only after your system has applied or intentionally and safely skipped the fixes. Return a non-2xx response when the complete operation should be treated as failed.
7. Fix completion notification
After the actionable fix.apply request finishes, the same URL can receive a fix.generated completion event:
{
"id": "evt_65e5d1e6c83d4040",
"event": "fix.generated",
"created": 1785059060,
"data": {
"job_id": "optimization-job-456",
"workspace_id": "workspace-id",
"domain": "cms.example.com",
"status": "SUCCESS",
"summary": {
"total": 1,
"processed": 1,
"failed": 0
},
"result": {
"status": "success",
"id": "fix-operation-456",
"link": "https://cms.example.com/blog/best-ai-seo-tools"
}
}
}
A failed job uses:
{
"event": "fix.generated",
"data": {
"status": "FAILED",
"summary": {
"total": 1,
"processed": 0,
"failed": 1
},
"result": {
"error": "Webhook returned error status 500"
}
}
}
Completion events are notifications only. Record their status if needed and return any 2xx response.
8. Single-endpoint routing example
from fastapi import FastAPI, HTTPException, Request
app = FastAPI()
WEBHOOK_SECRET = "replace-with-the-configured-secret"
@app.post("/webhooks/nuwtonic")
async def nuwtonic_webhook(request: Request):
raw_body = await request.body()
signature = request.headers.get("X-ContentKit-Signature", "")
timestamp = request.headers.get("X-ContentKit-Timestamp", "")
if not verify_webhook(raw_body, signature, timestamp, WEBHOOK_SECRET):
raise HTTPException(status_code=401, detail="Invalid webhook signature")
payload = await request.json()
event_id = payload.get("id")
event = payload.get("event")
data = payload.get("data") or {}
if not event_id:
raise HTTPException(status_code=400, detail="Webhook event ID is missing")
if await event_was_processed(event_id):
return {"status": "already_processed"}
if event == "content.generated":
result = data.get("result") or {}
if "published_count" in result and "results" in result:
await record_publish_summary(payload)
await mark_event_processed(event_id)
return {"status": "summary_recorded"}
post = await publish_or_update_article(result)
await mark_event_processed(event_id)
return {"id": post.id, "link": post.url}
if event == "fix.apply":
operation = await apply_fixes(
target_url=data.get("target_url"),
fixes=data.get("fixes") or [],
)
await mark_event_processed(event_id)
return {"id": operation.id, "link": operation.url}
if event == "fix.generated":
await record_fix_completion(payload)
await mark_event_processed(event_id)
return {"status": "completion_recorded"}
# Acknowledge unknown notification events so future additions do not
# create repeated delivery failures. Log them for later review.
await record_unknown_event(payload)
await mark_event_processed(event_id)
return {"status": "ignored", "reason": "unsupported_event"}
The storage and CMS functions in this example are placeholders that must be implemented for your system.
9. Delivery and retry behavior
Actionable deliveries:
content.generatedarticle events use a 30-second timeout.fix.applyevents use a 30-second timeout.- Any HTTP status from
200through299is accepted. - A non-
2xxresponse causes that publish or fix operation to fail. - Direct actionable deliveries do not currently guarantee automatic webhook retries.
Completion notifications:
- use a 30-second timeout;
- retry failures up to three times with exponential backoff;
- can therefore arrive more than once.
Because retries and network ambiguity can always produce duplicates, idempotency is required for every event.
10. HTTP response guidance
Use these responses so users receive clear results:
200or201: operation completed successfully.202: accepted only if your system can safely finish asynchronously and has already persisted the operation.400: payload is invalid or required fields are missing.401: signature is missing or invalid.404:target_urlcannot be resolved.409: content changed after the audit and cannot be safely updated.422: a fix is valid JSON but unsupported by your CMS.429: temporary rate limit; includeRetry-Afterwhere possible.500or503: temporary server failure.
Return clear error JSON:
{
"error": "target_not_found",
"message": "No editable article matches data.target_url."
}
Do not return 2xx with an error body. Nuwtonic treats every 2xx response as successful.
11. Production checklist
- Use a public HTTPS endpoint.
- Store the webhook secret securely.
- Verify signatures against the raw body.
- Enforce a timestamp tolerance.
- Deduplicate using the event ID.
- Distinguish actionable articles from publish summaries.
- Return the final article URL for publish requests.
- Resolve fix target URLs exactly.
- Check
current_valuebefore replacing content. - Preserve image references when changing alt text.
- Validate and deduplicate JSON-LD.
- Log event ID, event type, target URL, response status, and processing result.
- Keep processing below 30 seconds or persist work before returning
202. - Monitor non-
2xxresponses and completion events.
12. Current limitations
- One URL receives all publish, fix, and completion events; separate event URLs are not supported.
- The article object is a pass-through record, so optional fields vary by generation workflow.
- The same
content.generatedevent name is used for both actionable article delivery and publish completion summaries. Inspectdata.resultbefore processing. - The webhook integration sends instructions; your receiver is responsible for CMS-specific updates, conflict handling, publishing, and rollback.
- Returning
202does not provide a later callback API for your system to report its own asynchronous result. - Direct article and fix deliveries do not currently guarantee automatic retries.
