Skip to main content

Custom Webhook

Overview

The Custom Webhook integration lets outwrite.ai POST your finished content to any URL you provide. From there you can wire it into anything — Make, Zapier, n8n, Pipedream, a headless CMS, your own backend, an internal Slack notification, or any tool that accepts JSON over HTTPS.

Use this integration when:

  • Your CMS isn't WordPress, Shopify, or Webflow
  • You want to push content into Notion, Ghost, Strapi, Sanity, Contentful, Hashnode, Hugo, Jekyll, or any custom system
  • You want full control over how the content lands (formatting, post-processing, conditional logic, multi-destination fan-out)
  • You want to trigger an automation when an article is finished (notify a team member, append to a tracking sheet, kick off a review workflow)

What you get: A clean, predictable JSON payload with everything about your article — title, slug, content, featured image, author, publisher, meta description, and more — sent to your URL the moment you publish.


Getting Started

Step 1: Get a Webhook URL

You'll need a destination URL that accepts incoming POST requests. Common options:

  • Make.com — Create a scenario with a "Custom Webhook" trigger
  • Zapier — Create a Zap with a "Webhooks by Zapier → Catch Hook" trigger
  • n8n — Add a Webhook node and copy the production URL
  • Pipedream — Create a workflow with an HTTP/Webhook trigger
  • Your own server — Any HTTPS endpoint that returns 2xx for incoming POST requests

The URL itself is the secret — anyone who has it can send content to your destination, so treat it like a password.


Step 2: Connect the Webhook in outwrite.ai

  1. Go to Settings → Integrations
  2. Find the Custom Webhook section and expand it
  3. Paste your webhook URL into the Webhook URL field
  4. Choose your HTML mode:
    • Cleaned (default) — outwrite normalizes the HTML before sending (recommended for most platforms)
    • Raw — full, unprocessed HTML (recommended when your platform can handle tables and rich markup, or when you want to post-process the HTML yourself)
  5. Click Save

Step 3: Send a Test Payload

Click the Send Test button. outwrite.ai sends a fully-formed sample article to your webhook so you can:

  • Confirm the URL is reachable
  • See the exact payload structure
  • Map fields to your destination
  • Test downstream automations before going live

The test payload is identical to a real publish in shape — only the article contents are placeholder text and is_draft is set to true.


Step 4: Publish Content Through the Webhook

Once the webhook is connected:

  1. Open any article in your Library
  2. Click Publish
  3. Choose Send to Webhook from the dropdown
  4. Toggle Draft or Publish as needed
  5. Click Send

Your content is sent to your webhook URL within a few seconds.


Cleaned vs. Raw HTML

The biggest decision when setting up a webhook is which HTML mode to use. Pick based on what your destination accepts.

Cleaned mode (default)

Best for: Notion, Ghost, most headless CMSes, email tools, simple blog platforms, Webflow CMS Rich Text fields.

In cleaned mode, outwrite.ai applies these transformations before sending:

  • H1 stripped from the body — the title is sent as a separate title field, so duplicating it in the body would be redundant
  • Tables converted to bullet lists — many rich-text editors don't support <table>. Each row becomes a list item with the row's first cell bolded and the remaining cells listed below it
  • Image captions normalized<figure> blocks get a clean <figcaption> containing alt text or photographer credit
  • Links simplified — only the href is kept; external links automatically get target="_blank"
  • List whitespace minified — list HTML is compacted so destination parsers don't break on pretty-printed whitespace

Cleaned mode is the safe default. If you're not sure, start here.

Raw mode

Best for: Custom systems where you control the rendering, full-fidelity migration into a CMS that supports tables, or any case where you want to post-process the HTML yourself.

In raw mode, outwrite.ai sends the article's HTML exactly as it appears in your Library — tables included, all attributes preserved, full structure intact.

Use raw when:

  • You need tables to survive (comparison articles, pricing pages, structured data)
  • Your destination can render full HTML safely
  • You want to run your own cleaning/transformation step downstream

Payload Schema

Every payload sent to your webhook is a JSON object with this shape:

{
"title": "How to Optimize Your Content Strategy",
"slug": "how-to-optimize-your-content-strategy",
"excerpt": "A short summary of the article — usually the meta description, or the first ~160 characters of plain text if no meta description exists.",
"content": "<h2>Introduction</h2><p>...</p><ul><li>...</li></ul>",
"content_type": "cleaned",
"featured_image_url": "https://images.pexels.com/photos/3184291/pexels-photo-3184291.jpeg",
"featured_image_alt": "Photo by Jane Doe from Pexels",
"word_count": 1450,
"author": "Benjamin Smith",
"author_url": "https://yourbrand.com/team/benjamin",
"author_bio": "Founder of YourBrand. Writes about content strategy and AI search.",
"publisher_name": "YourBrand",
"publisher_url": "https://yourbrand.com",
"publisher_logo_url": "https://yourbrand.com/logo.png",
"published_at": "2026-05-08T13:42:00.000Z",
"is_draft": false,
"content_id": "c345c21d-448a-4fa1-82f6-2a1cfda449b8",
"meta_description": "A short SEO-optimized description of the article.",
"source": "outwrite.ai"
}

Field Reference

FieldTypeWhat it contains
titlestringThe article headline
slugstringURL-safe version of the title (lowercase, hyphenated)
excerptstringShort summary — meta description if present, otherwise the first ~160 characters
contentstring (HTML)The article body. Cleaned or raw depending on your HTML mode
content_typestring"cleaned" or "raw" — tells your downstream system which mode it received
featured_image_urlstring | nullURL of the featured image (Pexels stock photo, uploaded image, or AI-generated image)
featured_image_altstringAlt text. For Pexels stock images this is auto-set to "Photo by {photographer} from Pexels"
word_countintegerTotal word count of the article
authorstring | nullAuthor name from your default Author/Publisher preset
author_urlstring | nullAuthor profile or bio page URL
author_biostring | nullShort author biography
publisher_namestring | nullPublishing organization name
publisher_urlstring | nullPublisher website
publisher_logo_urlstring | nullPublisher logo URL (used for schema.org metadata in many CMSes)
published_atstring (ISO 8601)Publication timestamp
is_draftbooleantrue if the Draft toggle was on when sending; false if Publish was chosen
content_idstring (UUID)A stable identifier for this piece of content, useful for deduplication or upsert
meta_descriptionstring | nullSEO meta description, auto-generated if not already saved
sourcestringAlways "outwrite.ai" — useful for filtering or verifying payload origin

Set up your Author/Publisher info under Settings → Brand Kit so author and publisher fields are populated automatically on every payload.


Request Details

PropertyValue
HTTP methodPOST
Content-Typeapplication/json
AuthenticationNone — the URL itself is the secret
Expected replyAny 2xx response within 30 seconds
TimeoutIf your endpoint doesn't reply within 30 seconds, the send is treated as failed
RetriesIf the endpoint returns a 5xx error, outwrite.ai retries with exponential backoff

If your endpoint returns a non-2xx response, you'll see a clear error in the outwrite.ai UI so you can fix it and re-send.


Verifying Where a Payload Came From

Every webhook payload includes "source": "outwrite.ai". Use it as a sanity check on your receiver so unrelated traffic to the same URL is rejected.

// Example: Express receiver
app.post("/incoming", express.json({ limit: "5mb" }), async (req, res) => {
const payload = req.body;

if (payload.source !== "outwrite.ai") {
return res.status(400).send("Unrecognized source");
}

// ...your logic here
res.json({ ok: true });
});

For stronger guarantees (replay protection, signed payloads), put your webhook behind a secret URL slug, an IP allowlist, or an automation platform that handles auth for you (Make, Zapier, etc.).


Common Integration Recipes

Send to Notion

Use a Make or Zapier scenario with two steps:

  1. Custom Webhook trigger — paste the URL into outwrite.ai
  2. Notion → Create Database Item — map outwrite fields to your database properties:
    • Name ← title
    • Slug ← slug
    • Body ← content (Notion accepts cleaned HTML or you can convert to Markdown first)
    • Cover ← featured_image_url

Send to Ghost

Wire the webhook into a Ghost-targeted Zap:

  • Title ← title
  • Slug ← slug
  • HTML ← content (use cleaned mode)
  • Feature image ← featured_image_url
  • Status ← is_draft ? "draft" : "published"
  • Custom excerpt ← meta_description

Send to a Custom Backend (Node.js)

import express from "express";
const app = express();

app.post("/outwrite", express.json({ limit: "5mb" }), async (req, res) => {
const p = req.body;
if (p.source !== "outwrite.ai") return res.status(400).send("bad source");

await myCMS.createPost({
title: p.title,
slug: p.slug,
body: p.content,
summary: p.excerpt,
cover: p.featured_image_url,
coverAlt: p.featured_image_alt,
author: { name: p.author, url: p.author_url, bio: p.author_bio },
publishAt: p.is_draft ? null : p.published_at,
seo: { description: p.meta_description }
});

res.json({ ok: true });
});

app.listen(3000);

Send to a Headless CMS (Strapi / Sanity / Contentful)

Use raw mode if you want full HTML fidelity, then in your handler:

  1. Verify source === "outwrite.ai"
  2. Slugify or accept slug as-is
  3. Map content to your richtext field (most headless CMSes have an HTML-to-richtext converter, or you can store raw HTML)
  4. Upsert by content_id to handle re-publishes

Notify Slack or Email When an Article is Ready

Use Zapier/Make to:

  1. Receive the webhook
  2. Filter to is_draft === false
  3. Send a Slack message: New article published: {{title}} — {{publisher_url}}/{{slug}}

Multi-destination fan-out

A single webhook URL can drive multiple downstream actions in Make/Zapier — push to your CMS, log to a Google Sheet, post to Slack, and ping your analytics endpoint, all from one trigger.


Choosing Between the Webflow and Custom Webhook

Both integrations send the same kind of JSON, but they're tuned slightly differently:

IntegrationBest for
WebflowUse this if you're publishing into Webflow CMS specifically. Pre-built Make.com template included.
Custom WebhookEverything else — Notion, Ghost, headless CMSes, custom backends, internal automations.

Both are URL-based. The main differences: the Custom Webhook gives you a content_type field (so your handler knows whether HTML is cleaned or raw) and uses snake_case field names (is_draft instead of isDraft).


Troubleshooting

My webhook didn't fire.

  • Confirm a webhook URL is saved in Settings → Integrations → Custom Webhook
  • Try Send Test first — if the test arrives but real publishes don't, the issue is in your destination (filters, paused scenario, etc.)

The destination got the payload but my CMS post is empty.

  • You're probably mapping content to a plaintext field. Map it to a Rich Text or HTML field instead
  • If the field strips HTML, switch to Cleaned mode

Tables look broken in my CMS.

  • In Cleaned mode, tables are converted to bullet lists by design (most rich-text editors can't handle <table>)
  • Switch to Raw mode if your destination supports tables

Lists are showing as one big paragraph.

  • This usually means the destination's HTML parser is struggling with whitespace. Cleaned mode minifies list whitespace specifically to fix this — make sure you're not in raw mode

My endpoint returned a 5xx and the article was marked failed.

  • outwrite.ai retries 5xx errors automatically with backoff. If the error persists, check your endpoint logs for the underlying issue
  • Confirm your endpoint replies within 30 seconds

I want to revoke the webhook.

  • Delete the URL in Settings → Integrations → Custom Webhook, or rotate the URL on the destination side (in Make/Zapier/n8n) so old senders can no longer reach it

Pro Tips

  • Always run Send Test first. It's the fastest way to confirm everything is wired up before publishing real content.
  • Set up your Author and Publisher details before publishing so author/publisher fields are populated automatically on every payload — handy for schema.org metadata.
  • Use content_id for deduplication. If you re-publish the same article (after edits), the content_id stays the same. Upsert on this field to avoid duplicates.
  • Filter on source in your handler. A simple if (payload.source !== "outwrite.ai") reject prevents accidental cross-traffic.
  • Pick the right HTML mode once and forget it. Switching modes changes the payload shape (content_type field) — set it based on your destination and leave it alone.
  • Treat the URL as a secret. Anyone with the webhook URL can post to your destination. If it leaks, rotate it on the destination side.