blogr.ai

Docs

Webhooks

Receive every published article and inner page as a JSON POST to your own endpoint — the fastest way to connect blogr.ai to any CMS, static site, or custom stack.

How it works

When you click Publish on an article, blogr.ai sends it to your endpoint as a single JSON POST request. Your endpoint stores or publishes the article on your side and responds with a 2xx status code. That's the whole contract — no SDK, no polling.

Let your AI write the endpoint

You don't have to write the receiver yourself. Copy this prompt into your AI coding assistant (Cursor, Claude Code, Copilot, ChatGPT) and it builds the endpoint in your project's own language and style. The full payload contract below is baked in.

I use blogr.ai to write and publish articles for my website. blogr.ai can deliver every published article to a webhook on my site — blog posts, and the site pages it designs from its topical map (pricing, comparisons, services, locations). Build that webhook receiver in this project. Match the language, framework, and conventions this project already uses.

How blogr.ai delivers:
- An HTTP POST with a JSON body, sent to a public https URL that I will configure in blogr.ai.
- Every request carries my access token in an "Authorization: Bearer" header. Read the expected token from an environment variable or secret store, never hardcode it. I will copy the token from the Webhooks card in blogr.ai.
- Test deliveries carry "test": true with a sample article. Real deliveries carry "test": false.

The JSON body:

{
  "event": "article.published (a blog post) or page.published (a site page)",
  "test": false,
  "article": {
    "id": 123,
    "type": "post or page",
    "title": "Article title",
    "slug": "article-title",
    "path": "null for a post; the site path for a page, e.g. /compare/acme-alternatives/",
    "content_markdown": "# Markdown body...",
    "content_html": "<h1>HTML body...</h1>",
    "seo": {
      "title": "SEO title, or null",
      "meta_description": "Meta description, or null"
    },
    "og_image_url": "https://... social image, or null",
    "og_image_alt": "Image alt text, or null",
    "target_keyword": "keyword, or null",
    "categories": ["Category"],
    "tags": ["tag"],
    "published_at": "2026-01-01T00:00:00+00:00"
  },
  "website": {
    "domain": "example.com"
  }
}

What the receiver must do:
1. Compare the bearer token against my stored token and respond 401 on a mismatch.
2. Respond 200 to test deliveries ("test": true) without saving anything.
3. For real deliveries, create or update the content on my site from the payload. Match on "article.id" or "article.slug" so a repeat delivery updates the existing item instead of duplicating it.
4. A "type" of "post" is a blog post: publish it in the blog as the site already does. A "type" of "page" is a site page: serve it at "path" exactly, outside the blog (for example "/pricing/" at /pricing, "/compare/acme-alternatives/" at /compare/acme-alternatives), never list it among blog posts, and never overwrite a hand-built route that already exists at that path — skip the page and respond 200 with the existing URL instead.
5. Respond with a 2xx status on success. A JSON response body of {"url": "..."} is optional; when present, blogr.ai saves it as the live URL — https://my-site.com/blog/article-title for a post, https://my-site.com followed by "path" for a page.
6. blogr.ai treats any non-2xx response as a failed delivery, so only error out when something really failed.

"content_html" is ready to render as-is. "content_markdown" is the same body in Markdown, in case my site renders Markdown itself.

When you are done, tell me:
1. The full public URL of the webhook, so I can paste it into the Webhooks card in blogr.ai.
2. Where I paste my access token (the environment variable or secret name).

Connect your webhook

  1. In your dashboard, open Setup → Publishing and choose Webhooks.
  2. Enter your endpoint URL — it must be a public https URL, not a private or internal address.
  3. Copy the access token we generate for you into your endpoint's configuration. blogr.ai sends it with every delivery so your endpoint can verify the request really came from us.
  4. Click Send test to deliver a sample article, then Save.

A website publishes to one destination: connecting the webhook replaces any other integration on that website.

The delivery request

Every delivery — test or real — is an HTTP POST with these headers:

POST /your-endpoint HTTP/1.1
Host: api.example.com
Authorization: Bearer <your access token>
Content-Type: application/json
Accept: application/json
  • Redirects are not followed — the endpoint URL must respond directly.
  • Deliveries time out after 15 seconds. Respond fast and process the article asynchronously if you need to.
  • Any 2xx response counts as delivered. Anything else — or no response — marks the article as failed to publish.

The payload

The request body is one JSON object:

{
  "event": "article.published",
  "test": false,
  "article": {
    "id": 812,
    "type": "post",
    "title": "How to Choose a Laravel Hosting Provider",
    "slug": "how-to-choose-a-laravel-hosting-provider",
    "path": null,
    "content_markdown": "# How to Choose a Laravel Hosting Provider\n\nPicking a host…",
    "content_html": "<h1>How to Choose a Laravel Hosting Provider</h1>\n<p>Picking a host…</p>",
    "seo": {
      "title": "How to Choose a Laravel Hosting Provider (2026 Guide)",
      "meta_description": "Compare Laravel hosting providers on speed, pricing, and deploys."
    },
    "og_image_url": "https://blogr.ai/storage/articles/laravel-hosting-9f2c41ab.png",
    "og_image_alt": "How to Choose a Laravel Hosting Provider (2026 Guide) — laravel hosting",
    "target_keyword": "laravel hosting",
    "categories": ["Hosting", "Laravel"],
    "tags": ["laravel hosting"],
    "published_at": "2026-07-08T09:30:00+00:00"
  },
  "website": {
    "domain": "example.com"
  }
}
Field Meaning
eventarticle.published for a blog post, page.published for an inner page from your topical map — see Blog posts and inner pages below. Check it before processing so future event types don't surprise your endpoint.
testtrue for test deliveries from the setup screen, false for real articles.
article.idThe article's stable ID in blogr.ai — use it to de-duplicate retried deliveries. 0 in test deliveries.
article.type"post" for a blog post, "page" for an inner page from the topical map. A page belongs on your site at article.path, outside the blog, and should never appear among blog posts.
article.slugURL-ready slug, taken from the SEO settings or derived from the title. For a page, the last segment of its path.
article.pathnull for a post. For a page, the site path it belongs at, with slashes on both ends — /pricing/, /compare/acme-alternatives/.
article.content_markdownThe article body as Markdown — the canonical source.
article.content_htmlThe same body rendered to sanitized HTML, ready to store as-is.
article.seoThe SEO title and meta description. Either can be null when not set.
article.og_image_urlPublic URL of the generated share image — keyword-named, metadata-free. Null when none was produced.
article.og_image_altKeyword-optimized alt text for the image, ready to use as-is.
article.target_keywordThe search query the article targets, or null.
article.categoriesUp to 3 categories generated with the article, as an array of strings. Empty when none were generated.
article.tagsUp to 3 tags generated with the article, as an array of strings. Empty when none were generated.
article.published_atISO 8601 timestamp of the delivery.
website.domainThe domain of the blogr.ai website the article belongs to — useful when one endpoint receives several sites.

Blog posts and inner pages

Your endpoint receives two kinds of content. Check event or article.type before you publish anything.

Blog posts arrive as article.published with type "post" and belong in your blog. Inner pages — the site-level pages your topical map designs, such as pricing, comparisons, services, and locations — arrive as page.published with type "page" and belong on the site itself, at the path in article.path, outside the blog.

An endpoint built before pages existed treats every delivery as a blog post. Add the check before publishing pages through it, or paste the current prompt above into your coding tool to update the receiver.

A page delivery looks like an article delivery, with the event, type, and path set:

{
  "event": "page.published",
  "test": false,
  "article": {
    "id": 913,
    "type": "page",
    "title": "Acme Alternatives: 7 CRMs Compared",
    "slug": "acme-alternatives",
    "path": "/compare/acme-alternatives/",
    "content_html": "<h1>Acme Alternatives</h1>…",
    "seo": { "title": "…", "meta_description": "…" },
    "categories": [],
    "tags": []
  },
  "website": { "domain": "example.com" }
}
Blog post Inner page
eventarticle.publishedpage.published
article.typepostpage
article.pathnullThe site path, slashes on both ends — /pricing/, /compare/acme-alternatives/
article.slugFrom the SEO settings, or derived from the titleThe last segment of path
Where it goesYour blog, e.g. /blog/{slug}Exactly at path, never listed among posts. Leave a hand-built route at that path alone.
Respond with{"url": "https://example.com/blog/{slug}"}{"url": "https://example.com{path}"}

Test deliveries

The Send test button on the setup screen delivers a fake article with the exact shape shown above, flagged with "test": true. Build your endpoint against the test delivery and it's ready for the real thing — just make sure test deliveries don't end up published on your site.

Responding to a delivery

Respond with any 2xx status code to confirm the delivery. If your response is JSON and includes a url, blogr.ai records it as the article's published URL:

{
  "url": "https://example.com/blog/how-to-choose-a-laravel-hosting-provider"
}

Failed deliveries and retries

If your endpoint is unreachable or responds with a non-2xx status, the article is marked "Failed to publish" in your workspace with the reason on the article row — for example "Your webhook endpoint responded 500". Nothing is lost: fix the endpoint and click Retry publish to deliver the article again.

Retries resend the full payload with the same article.id, so an endpoint that already stored the article can treat the retry as an update or ignore it.

Securing your endpoint

  • Verify the Authorization header on every request: it must equal Bearer followed by your access token. Reject anything else with a 401.
  • The token is generated for you and stored encrypted — treat it like a password, and rotate it anytime by entering a new value in Setup → Publishing.
  • Serve the endpoint over https. blogr.ai refuses to deliver to private or internal addresses.