Custom webhook integration
Receive an article payload on your own website, CMS, serverless function, or automation platform when AuthorityWriter delivers an article.
Setup
- Create a public HTTPS endpoint on your website.
- Configure the endpoint URL in AuthorityWriter Website Settings → Custom Webhook.
- Configure a strong webhook secret.
- Read the raw request body before parsing JSON.
- Verify the HMAC signature before processing the article.
- Return a
2xxresponse after successful validation and processing.
Request
AuthorityWriter sends an HTTP POST to your configured URL.
POST https://your-site.example/api/authoritywriter/webhook
Content-Type: application/json
Accept: application/json
X-AuthorityWriter-Event: article.published
X-AuthorityWriter-Signature: <hex HMAC-SHA256>Payload example
{
"event": "article.published",
"timestamp": "2026-09-10T20:00:00+00:00",
"article_id": 428,
"website_id": 12,
"website_name": "Example Website",
"title": "How to Choose Running Shoes for Flat Feet",
"slug": "running-shoes-for-flat-feet",
"focus_keyword": "running shoes for flat feet",
"secondary_keywords": ["stability shoes", "arch support"],
"format": "buying_guide",
"html_content": "<p>Article HTML...</p>",
"plain_content": "Article text...",
"meta_description": "Learn how to choose comfortable running shoes for flat feet.",
"featured_image": "https://cdn.example.com/images/article-header.jpg",
"featured_image_alt": "Running shoes selected for flat feet",
"inline_images": [
{
"url": "https://authoritywriter.com/storage/article-images/ai-img-example.jpg",
"alt": "Illustration explaining stability features for flat feet",
"order": 1,
"section_title": "Choosing Stability Features",
"placement": "after_section_heading"
}
],
"category": "Running Gear",
"sub_category": "Buying Guides",
"schema_markup": {"@type": "Article"},
"target_status": "draft"
}Payload fields
| Field | Type | Description |
|---|---|---|
| event | string | Currently article.published. |
| timestamp | ISO 8601 | Time the webhook payload was generated. |
| article_id | integer | AuthorityWriter article ID. |
| website_id | integer | AuthorityWriter website ID. |
| title / slug | string | Article title and URL slug. |
| focus_keyword | string | Primary SEO keyword. |
| secondary_keywords | string[] | Supporting SEO keywords. |
| format | string | Article format such as guide or buying_guide. |
| html_content | string | HTML article content, including schema markup when enabled. |
| plain_content | string | Text-only article content. |
| meta_description | string | Generated description, limited to approximately 160 characters. |
| inline_images | object[] | Unique in-article image references extracted from html_content. Each item has url and alt. |
| category | string|null | Primary article category. Map this to your CMS category, taxonomy, or collection. |
| sub_category | string|null | Optional child category. Map this beneath category where your CMS supports hierarchical taxonomies. |
| schema_markup | object|null | Decoded JSON-LD schema when enabled. |
| target_status | string | Configured publishing status, usually draft or publish. |
HTML content and supported elements
The html_content field contains the article body as HTML, not Markdown. Your receiver should store or render this field as HTML after signature verification and sanitization. Apply your own site stylesheet to the elements below; AuthorityWriter does not require a specific CSS framework or class naming convention.
| Element | How it is used | Recommended receiver styling |
|---|---|---|
| h2, h3 | Article section and subsection headings. | Add spacing, readable font sizes, and an anchored heading hierarchy. |
| p, strong, em | Paragraphs, bold emphasis, and italic emphasis. | Set line-height, paragraph spacing, and accessible contrast. |
| ul, ol, li | Bulleted and numbered lists. | Restore list markers and left padding; do not flatten into plain text. |
| a | Links included in the article. | Style links visibly and preserve the href and link text. |
| blockquote | Quoted or highlighted guidance. | Use a visible border/background and distinguish it from normal paragraphs. |
| table, thead, tbody, tr, th, td | Comparison tables, specifications, statistics, and feature summaries. | Use responsive horizontal scrolling on small screens, borders, cell padding, and a distinct header row. |
| figure, img, figcaption | In-article images with alt text and captions. | Make images responsive, preserve alt text, and style captions below the image. |
| div | Layout wrappers, including responsive video wrappers. | Allow only the classes/attributes you trust or map them to your own design system. |
| script[type=application/ld+json] | Schema markup may be appended when schema markup is enabled. | Keep it in the document head or body as valid JSON-LD; do not display it as article text. |
html_content as untrusted input. Verify the HMAC first, then sanitize HTML with an allowlist. Never allow arbitrary scripts, event-handler attributes such as onclick, unsafe URLs, or untrusted iframe sources. Escape content if your CMS stores plain text instead of HTML.Example HTML sent in html_content
<h2>Choosing the right option</h2>
<p>A short explanation with <strong>important terms</strong>.</p>
<ul><li>First benefit</li><li>Second benefit</li></ul>
<table><thead><tr><th>Feature</th><th>Details</th></tr></thead><tbody><tr><td>Support</td><td>All-day comfort</td></tr></tbody></table>
<figure><img src="https://..." alt="Descriptive image text"><figcaption>Optional caption</figcaption></figure>YouTube video embeds
When the article includes a relevant YouTube video, the HTML can contain a responsive iframe embed. The video is not uploaded to your website; the iframe loads the video from YouTube. Your receiver should preserve the iframe or convert it to the embed format supported by its CMS.
<div class="aspect-video my-4 rounded-xl overflow-hidden">
<iframe src="https://www.youtube.com/embed/VIDEO_ID" class="w-full h-full" frameborder="0" allowfullscreen></iframe>
</div>| Attribute | Purpose | Receiver guidance |
|---|---|---|
| src | YouTube embed URL containing the video ID. | Allow only https://www.youtube.com/embed/... or your approved YouTube embed host. |
| allowfullscreen | Allows viewers to expand the video. | Preserve it if your sanitizer supports boolean iframe attributes. |
| class | Responsive sizing and visual styling. | Keep the classes if using Tailwind, or replace them with your own responsive video CSS. |
If your CMS strips iframes, extract the YouTube video ID from the approved embed URL and use your CMS’s native YouTube block/oEmbed feature. Do not permit arbitrary iframe domains or JavaScript URLs.
Category mapping
The article category is sent separately from the article body. Use category as the primary taxonomy and sub_category as its child taxonomy when your CMS supports hierarchical categories.
// Example mapping
$category = $payload['category'] ?? null;
$subCategory = $payload['sub_category'] ?? null;
$parentId = find_or_create_category($category);
$childId = $subCategory
? find_or_create_category($subCategory, $parentId)
: null;
assign_article_taxonomy($articleId, $parentId, $childId);If either value is null, do not create an empty category. Custom integrations may map these values to categories, tags, collections, folders, or another taxonomy system.
Each item in inline_images includes placement metadata. Use section_title to match the image to the corresponding <h2> in html_content, and use order as the fallback sequence.
| Field | Meaning | Receiver action |
|---|---|---|
| order | 1-based image order in the article. | Use when matching by sequence. |
| section_title | Nearest preceding H2 section title. | Find the exact H2 in html_content. |
| placement | after_section_heading or inline_content. | Insert after that H2, or preserve the original HTML position. |
| alt | Descriptive alternative text. | Preserve it when importing the image. |
// Recommended placement algorithm
foreach ($payload['inline_images'] as $image) {
$imageUrl = import_to_media_library($image['url']);
$alt = $image['alt'];
$heading = $image['section_title'];
// Find the matching H2 in html_content and place the image
// immediately after its opening section, preserving order.
$html = insert_after_h2($html, $heading, $imageUrl, $alt);
}Important: The original html_content already contains the correct image position. The metadata is a reliable guide for importing the image and rewriting its URL. Do not append all images at the bottom of the article.
The signature is a lowercase hexadecimal HMAC-SHA256 digest of the exact raw JSON request body, calculated with your webhook secret.
// PHP
$rawBody = file_get_contents('php://input');
$secret = getenv('AUTHORITYWRITER_WEBHOOK_SECRET');
$expected = hash_hmac('sha256', $rawBody, $secret);
$provided = $_SERVER['HTTP_X_AUTHORITYWRITER_SIGNATURE'] ?? '';
if (!$provided || !hash_equals($expected, $provided)) {
http_response_code(401);
exit('Invalid signature');
}
$payload = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
// Process $payload only after signature verification.
http_response_code(200);Image transfer
Images are transferred as references in the JSON payload, not as multipart uploads.
Featured image
The featured_image field contains either an HTTPS image URL or a supported data URL. Your website should download the URL, store it in its own media library, and use featured_image_alt for accessibility.
Inline images
Inline images are already referenced inside html_content. Extract each <img src="...">, download remote URLs if required, rewrite the URL to your local media URL, and preserve the alt attribute.
Recommended receiver flow
- Verify the webhook signature using the raw request body.
- Read
featured_image; download it over HTTPS with a timeout and file-size limit. - Validate the downloaded MIME type and image dimensions before saving.
- Store the image in your media library and attach it as the article’s featured image.
- Parse
html_contentand optionally import inlineimgsources. - Rewrite imported image URLs and preserve each supplied alt text.
Return any successful 2xx response after processing. Return 401 for an invalid signature and a non-2xx response for temporary processing failures. Make your receiver idempotent using article_id plus slug, because a delivery may be retried by the sender or network.
HTTP/1.1 200 OK
Content-Type: application/json
{"received":true}