Developer guide · v1

Irizpro Publish API

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.

PHP or Node.js (Express) site? Skip the coding. In Irizpro → Settings → Publishing → Your Website, choose your technology and download the EasySync kit: a ready-made blog folder (endpoint, storage, blog list, article pages with SEO tags, sitemap) already set up with your keys. Upload it, click Check connection, done. This guide is for building the endpoint yourself on any other stack.

Quick start

  1. The site owner opens Irizpro → SEO Blog → Settings → Publish destination → Your Website (Publish API), enters the endpoint URL you will create, and clicks Connect. Irizpro issues the site’s Site ID and Signing Secret (its passport) and the owner sends them to you with “Copy setup for developer”.
  2. Download the receiver for your stack, set the two values as environment variables, and fill in the one function that saves a post in your system.
  3. Deploy it, then the site owner clicks Send test. Irizpro checks your endpoint against this guide and lists anything to fix. When all checks pass, articles flow automatically.

The request

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-Typeapplication/json
X-Irizpro-Specirizpro.publish.v1
X-Irizpro-Eventpost.publish or post.test
X-Irizpro-Site-IdYour Site ID
X-Irizpro-DeliveryUnique id of this request (log it)
X-Irizpro-TimestampUnix time in seconds when it was signed
X-Irizpro-Signaturesha256=<hex HMAC>, see “Verify the signature”

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_ID

Example in PHP: hash_equals('sha256=' . hash_hmac('sha256', $ts . '.' . $raw, $secret), $sig). All receivers below already do this.

The article payload

{
  "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"
  }
}
FieldTypeHow to use it
post.external_idstringStable id of the article. The same id arrives again when an article is re-published: update that post, never create a duplicate.
post.slugstringURL-safe slug (a-z, 0-9, hyphens). Use it for the post URL.
post.titlestringThe article title. Print it as the page’s only <h1>.
post.content_htmlstringThe 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_htmlstringThe complete article including <h1> and hero image, for sites that render it as-is. Use either content_html or full_html, not both.
post.excerptstring | nullShort summary for listings and cards.
post.seo.meta_titlestringUse for <title> and og:title.
post.seo.meta_descriptionstring | nullUse for <meta name="description"> and og:description.
post.seo.focus_keyword / keywordsstring / string[]Optional, for your SEO plugin or tags.
post.featured_image{ url, alt } | nullHosted https image. Show it once (hero) and use it for og:image. You may download it to your own storage.
post.author{ name } | nullByline, if one is set in Irizpro.
post.categories / tagsstring[]Optional taxonomy.
post.faqs{ question, answer }[]Already included in the HTML; provided separately in case you render FAQ blocks.
post.schema.article / faqobject | nullReady JSON-LD. Output each inside <script type="application/ld+json"> if your template doesn’t generate its own.
post.published_atISO 8601Publish 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.

Test vs publish

  • 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.

Your reply

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" }
StatusWhen
200Saved (created or updated), or test validated: { "ok": false, "error": "…" }
400Malformed body / unsupported spec / unknown event: { "ok": false, "error": "…" }
401Bad signature, stale timestamp or wrong site_id: { "ok": false, "error": "…" }
422A required field is missing or invalid (say which in "error"): { "ok": false, "error": "…" }
500Your 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.

Ready-made receivers

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.

Go-live checklist

  • ✓Endpoint is reachable over HTTPS (no redirect on POST).
  • ✓Every request’s signature, timestamp and site_id are verified; anything else gets 401.
  • ✓post.test deliveries return { "ok": true, "test": true } and are NEVER saved.
  • ✓post.publish creates the post, or updates it when post.external_id already exists.
  • ✓The reply is JSON: { "ok": true, "url": "<public URL of the post>", "id": "…", "action": "created" | "updated" }.
  • ✓The post page has exactly one <h1> (the title) and shows the featured image once.
  • ✓The page outputs <title>, meta description, canonical and og:title / og:description / og:image from the post fields.
  • ✓The new post appears in the blog listing and the XML sitemap.
  • ✓The Signing Secret is kept out of git (environment variable or a config file outside the web root).

“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.

Security

  • Treat the Signing Secret like a password: environment variable or a config file outside the web root, never in git or front-end code.
  • If it leaks, the site owner clicks New secret in Irizpro; the old one stops working at once. Update your server, then Send test.
  • Sanitize HTML to your site’s rules if you allow only certain tags. Irizpro never sends scripts inside content_html.
  • Your endpoint only needs to accept requests from Irizpro; the signature check is what enforces that.

FAQ

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