Webhook Events
When you publish a blog, update it, delete it, or complete a product optimization, WUDO sends a signed HTTP POST to your webhook endpoint. This page documents every event type and its payload.
How Delivery Works
Every webhook request includes three headers:
X-Wudo-Signature: sha256={hex_digest}
X-Wudo-Event: blog.published
User-Agent: Wudo-Webhook/1.0- •Signature — HMAC-SHA256 of the raw request body using your webhook secret
- •Content-Type — always
application/json - •Field naming — all payload fields use
snake_case, null values are omitted - •Timeout — WUDO waits up to 120 seconds for your endpoint to respond (to allow image downloads)
Events
blog.publishedSent when you click "Publish" in the dashboard. Contains the full blog content, images, CSS, JavaScript, and all SEO metadata needed to render the post on your website.
{
"event": "blog.published",
"timestamp": "2026-02-15T14:30:00Z",
"data": {
"title": "10 Web Design Trends for 2026",
"slug": "10-web-design-trends-2026",
"html_content": "<article>Full HTML content...</article>",
"css_content": "body { ... }",
"js_content": "...",
"excerpt": "Discover the latest web design trends...",
"meta_title": "10 Web Design Trends for 2026 | YourSite",
"meta_description": "Discover the latest web design trends...",
"author": "Wudo AI",
"tags": ["web design", "trends"],
"keywords": ["web design", "2026", "trends"],
"category": "Web Design",
"featured_image_url": "https://wudoseo.com/api/v1/blogs/images/{id}/banner.webp",
"og_image_url": "https://wudoseo.com/api/v1/blogs/images/{id}/og.webp",
"pdf_url": "https://.../{id}/takeaway.pdf",
"schema_org_json": "{...}",
"reading_time_minutes": 8,
"word_count": 3500,
"publish_immediately": true,
"images": [
{
"url": "https://wudoseo.com/api/v1/blogs/images/{id}/section-1.webp",
"alt_text": "Modern dashboard layout example",
"type": "section"
}
]
}
}blog.updatedSent when you update a published blog. Contains only the changed fields (typically slug, html_content, css_content, js_content). Merge with your existing blog data.
{
"event": "blog.updated",
"timestamp": "2026-02-15T15:00:00Z",
"data": {
"slug": "10-web-design-trends-2026",
"html_content": "<article>Updated HTML...</article>",
"css_content": "body { ... }",
"js_content": "..."
}
}blog.deletedSent when a blog is deleted from the dashboard. Only contains the slug so you can remove it from your website.
{
"event": "blog.deleted",
"timestamp": "2026-02-15T16:00:00Z",
"data": {
"slug": "10-web-design-trends-2026"
}
}products.optimizedSent when a product optimization batch completes. Contains the optimized SEO data for each product in the batch.
{
"event": "products.optimized",
"timestamp": "2026-02-15T14:30:00Z",
"data": {
"products": [
{
"external_id": "prod-123",
"meta_title": "Premium Blue Cotton T-Shirt | Soft & Breathable",
"meta_description": "Experience ultimate comfort...",
"page_title": "Premium Blue Cotton T-Shirt",
"page_description": "<p>Rich HTML content...</p>",
"image_url": "https://...",
"handle": "blue-cotton-t-shirt"
}
],
"total_count": 1,
"exported_count": 1,
"failed_count": 0
}
}testSent when you click "Test Connection" in Settings → Integrations. Use this to verify your endpoint is reachable and signature verification works.
{
"event": "test",
"timestamp": "2026-02-15T14:30:00Z",
"data": {
"message": "Webhook receiver is working"
}
}Blog Payload Fields
| Field | Type | Description |
|---|---|---|
title | string | Blog title |
slug | string | URL-safe slug (use as your blog URL path) |
html_content | string | Full HTML (may be a complete document with <html> — extract <body> content) |
css_content | string? | CSS styles (scope to avoid leaking into your page layout) |
js_content | string? | JavaScript for interactive elements |
excerpt | string? | Short excerpt for blog listing cards |
meta_title | string? | SEO title for <title> tag |
meta_description | string? | SEO description for meta tag |
author | string? | Author name (configurable on Samurai+ plans) |
tags | string[]? | Blog tags |
keywords | string[]? | SEO keywords |
category | string? | Blog category |
featured_image_url | string? | Full URL to the featured/banner image |
og_image_url | string? | Full URL to 1200x630 social sharing image |
pdf_url | string? | Full URL to downloadable PDF takeaway |
schema_org_json | string? | JSON-LD structured data for SEO |
reading_time_minutes | int? | Estimated reading time |
word_count | int? | Total word count |
publish_immediately | bool | Whether the blog should go live immediately |
images | array? | All images with url, alt_text, and type |
Integration Tips
Extract body from HTML
The html_content field may contain a full HTML document (starting with <!DOCTYPE html>). Extract only the <body> content to embed it in your page layout. Also check for <style> tags in the <head> and merge them with css_content.
Scope the CSS
The CSS may contain global selectors like body, :root, or *. Wrap your blog in a container (e.g. .blog-content) and rewrite these selectors to target that container instead, so the blog styles don't affect your main site layout.
Download images locally
Image URLs in the payload point to wudoseo.com. For best performance, download them to your own storage and rewrite the URLs in the HTML content. This also ensures your blog works if the WUDO API is temporarily unavailable.
Handle partial updates
The blog.updated event only includes changed fields. Merge the update with your existing stored blog data rather than replacing the entire blog.
Signature Verification
import crypto from 'crypto';
function verifySignature(rawBody: string, signature: string, secret: string): boolean {
if (!signature?.startsWith('sha256=')) return false;
const receivedHex = signature.slice('sha256='.length);
const computedHex = crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(receivedHex, 'hex'),
Buffer.from(computedHex, 'hex')
);
} catch {
return false;
}
}
// Usage:
const body = await request.text();
const signature = request.headers.get('X-Wudo-Signature') ?? '';
if (!verifySignature(body, signature, process.env.WUDO_WEBHOOK_SECRET!)) {
return new Response('Invalid signature', { status: 401 });
}
const payload = JSON.parse(body);Need help?
- •See the Webhook Setup guide for step-by-step configuration
- •See Code Examples for complete implementations in multiple languages
- •Contact us at contact@wudoseo.com