Developer guide · v1
Connect any website to the Irizpro SEO platform. You add one HTTPS endpoint that follows this guide; from then on every article approved in Irizpro is delivered to it automatically and published on your site. It works with any technology: PHP, Node.js, Next.js, Python, a headless CMS or your own database.
Irizpro sends POST to your endpoint with a JSON body (UTF-8) and these headers. It waits up to 30 seconds for your reply and does not follow redirects.
| Content-Type | application/json |
| X-Irizpro-Spec | irizpro.publish.v1 |
| X-Irizpro-Event | post.publish or post.test |
| X-Irizpro-Site-Id | Your Site ID |
| X-Irizpro-Delivery | Unique id of this request (log it) |
| X-Irizpro-Timestamp | Unix time in seconds when it was signed |
| X-Irizpro-Signature | sha256=<hex HMAC>, see “Verify the signature” |
Every request is signed with your Signing Secret (HMAC-SHA256), the same scheme Stripe and GitHub use for webhooks. Verify it before doing anything else, on the raw body. Reject old timestamps to block replays.
timestamp = header "X-Irizpro-Timestamp"
signature = header "X-Irizpro-Signature" // "sha256=<hex>"
raw = the request body, exact bytes, BEFORE JSON parsing
reject (401) if |now - timestamp| > 300 seconds
expected = "sha256=" + hex( HMAC_SHA256( key = SIGNING_SECRET, message = timestamp + "." + raw ) )
reject (401) unless constant_time_equals(expected, signature)
reject (401) unless json.site_id == SITE_IDExample in PHP: hash_equals('sha256=' . hash_hmac('sha256', $ts . '.' . $raw, $secret), $sig). All receivers below already do this.
{
"spec": "irizpro.publish.v1",
"event": "post.publish", // or "post.test"
"test": false, // true for "post.test": validate, never save
"delivery_id": "6f1c0b1e-…", // unique per request
"site_id": "site_9a1b2c3d4e5f6a7b",
"sent_at": "2026-10-17T09:30:00.000Z",
"post": {
"external_id": "e6f6f634-ea21-4a85-af9d-eb8213e1d319", // stable per article: same id = update
"slug": "whatsapp-appointment-reminders-clinics",
"title": "How WhatsApp Appointment Reminders Cut No-Shows",
"content_html": "<p>…</p><h2>…</h2>…", // body: NO <h1>, NO hero image
"full_html": "<h1>…</h1><figure>…</figure><p>…</p>…", // complete article, if you prefer it
"excerpt": "Use WhatsApp reminders to cut no-shows…",
"seo": {
"meta_title": "WhatsApp Appointment Reminders | Reduce No-Shows",
"meta_description": "Use WhatsApp appointment reminders…",
"focus_keyword": "whatsapp appointment reminders",
"keywords": ["whatsapp appointment reminders", "clinic no-shows"]
},
"featured_image": { "url": "https://…/featured.jpg", "alt": "…" }, // or null
"author": { "name": "Nidhi" }, // or null
"categories": ["Insights"],
"tags": ["clinic no-shows"],
"faqs": [{ "question": "…", "answer": "…" }],
"schema": { "article": { "@type": "Article", … }, "faq": { "@type": "FAQPage", … } },
"published_at": "2026-10-17T09:30:00.000Z",
"language": "en"
}
}| Field | Type | How to use it |
|---|---|---|
| post.external_id | string | Stable id of the article. The same id arrives again when an article is re-published: update that post, never create a duplicate. |
| post.slug | string | URL-safe slug (a-z, 0-9, hyphens). Use it for the post URL. |
| post.title | string | The article title. Print it as the page’s only <h1>. |
| post.content_html | string | The body for your template: no <h1> and no hero image (print those yourself). Clean HTML: p, h2, h3, ul, ol, li, table, figure, img, a, strong, em, blockquote. |
| post.full_html | string | The complete article including <h1> and hero image, for sites that render it as-is. Use either content_html or full_html, not both. |
| post.excerpt | string | null | Short summary for listings and cards. |
| post.seo.meta_title | string | Use for <title> and og:title. |
| post.seo.meta_description | string | null | Use for <meta name="description"> and og:description. |
| post.seo.focus_keyword / keywords | string / string[] | Optional, for your SEO plugin or tags. |
| post.featured_image | { url, alt } | null | Hosted https image. Show it once (hero) and use it for og:image. You may download it to your own storage. |
| post.author | { name } | null | Byline, if one is set in Irizpro. |
| post.categories / tags | string[] | Optional taxonomy. |
| post.faqs | { question, answer }[] | Already included in the HTML; provided separately in case you render FAQ blocks. |
| post.schema.article / faq | object | null | Ready JSON-LD. Output each inside <script type="application/ld+json"> if your template doesn’t generate its own. |
| post.published_at | ISO 8601 | Publish date/time (UTC). |
New optional fields may be added to v1 over time; ignore fields you don’t use. Anything breaking would ship as v2 with its own spec value.
post.publish: create the post, or update it if post.external_id already exists. Never create two posts for the same external_id.post.test ("test": true): sent by the “Send test” button. Verify and validate everything, then reply { "ok": true, "test": true, "action": "validated" }. Never save a test.Reply with JSON. On success use HTTP 200 and include the post’s public URL; Irizpro shows it as the published link and opens the page to check it.
HTTP 200
{ "ok": true, "url": "https://example.com/blog/whatsapp-appointment-reminders-clinics", "id": "123", "action": "created" }| Status | When |
|---|---|
| 200 | Saved (created or updated), or test validated: { "ok": false, "error": "…" } |
| 400 | Malformed body / unsupported spec / unknown event: { "ok": false, "error": "…" } |
| 401 | Bad signature, stale timestamp or wrong site_id: { "ok": false, "error": "…" } |
| 422 | A required field is missing or invalid (say which in "error"): { "ok": false, "error": "…" } |
| 500 | Your system failed to save (say why in "error"): { "ok": false, "error": "…" } |
Irizpro shows your error text to the site owner, so make it specific. After a failure the owner can publish again; your update-by-external_id rule keeps that safe.
Each file verifies the signature, handles test deliveries, validates the payload and replies in the right format. You only implement the function that saves a post.
PHP
Any PHP host (Hostinger, cPanel, Laravel…)
Download irizpro-publish.php
Node.js
No dependencies; or reuse handle() in Express/Fastify
Download irizpro-receiver.js
Next.js
App Router route handler
Download route.ts
Python
Flask; handle() also fits Django/FastAPI
Download irizpro_receiver.py
“Send test” checks the first items automatically. After each real publish Irizpro also opens the live page and reports on the H1, featured image, meta tags, canonical and og:image.
content_html.Can I keep my own admin panel?
Yes. The endpoint just writes to the same place your admin does; posts from Irizpro appear alongside your own.
Should I download the images?
Optional. The URLs are permanent public links. Downloading them to your storage is fine; keep the alt text.
What if the slug already exists for a different post?
Reply 422 with a clear error (or adjust the slug and return the real URL). Never overwrite a post that has a different external_id.
Do I need WordPress, Shopify or GitHub?
No. Those have built-in connectors in Irizpro; this API is for every other kind of site.
Irizpro Publish API v1 · spec value irizpro.publish.v1