Publishing
Last updated
Every article the calendar finishes can leave RankWhy on its own, as a draft, never published: a WordPress draft (application password), a Ghost draft (Admin API key), a signed webhook to any endpoint you run, or an Atom feed behind a private URL. Set them in your account, Calendar tab, "Where finished articles go". Logins are stored encrypted with the server key and used for that one request.
The webhook
A POST with a JSON body and two headers: X-RankWhy-Timestamp (Unix seconds) and X-RankWhy-Signature (v1= followed by the hex HMAC-SHA256 of timestamp.body with your signing secret). Answer with any 2xx. No answer or a 5xx is retried after 5 and 30 seconds; a 4xx is taken as your answer. The secret is shown once when you create it; rotate it from the same place.
{
"event": "article.published", // or article.updated for a refresh day
"id": 42, "site": "example.com", "keyword": "hijri date today",
"slug": "hijri-date-today", "title": "...", "excerpt": "...",
"meta_title": "...", "meta_description": "...",
"html": "<p>...</p>", // no H1; your template adds the title
"markdown": "# ...",
"header_image_url": null,
"publish_at": "2026-11-06T00:00:00.000Z", "published_at": "2026-11-06T04:03:00.000Z", "updated_at": "2026-11-06T04:03:00.000Z"
}
Build the receiver with your coding agent
Paste this into Claude Code, Cursor, Lovable, Replit or any coding agent working on your site, then put the endpoint URL and the secret into RankWhy:
Add a blog to this site that receives articles from RankWhy.
1. Store articles (id, slug, title, excerpt, meta_title, meta_description, html, markdown, status, publish_at, published_at, updated_at); id is the key.
2. Add a POST route at /api/rankwhy-webhook. Read the raw request body as text. Verify the request before using it:
signature = request header X-RankWhy-Signature, timestamp = header X-RankWhy-Timestamp;
expected = "v1=" + hex(HMAC-SHA256(key = process.env.RANKWHY_WEBHOOK_SECRET, data = timestamp + "." + rawBody));
reject with 401 unless expected equals signature (constant-time compare) and the timestamp is within 5 minutes of now.
3. Parse the JSON. On event "article.published" insert the article with status "draft"; on "article.updated" replace the fields of the article with the same id and keep its slug. Answer 200 with {"ok":true}.
4. Add pages /blog (published articles, newest first) and /blog/[slug] (title as H1, then the html, meta title and description in the head). Drafts are not listed until I set status to "published".
5. Put RANKWHY_WEBHOOK_SECRET in the environment; never commit it.
Tell me the public URL of the webhook when done.
The Atom feed
Turn it on and you get a URL like https://rankwhy.com/feed/<token>.xml with the last 20 finished articles: title, summary, HTML content, and rankwhy:slug, rankwhy:keyword, rankwhy:metaDescription, rankwhy:markdown. Zapier ("RSS by Zapier"), Make and most CMS importers read it. The token is the only protection, so the feed holds drafts and you can rotate the URL at any time.
WordPress and Ghost
WordPress: a user with an application password; RankWhy creates a draft post through /wp-json/wp/v2/posts with the title, HTML and excerpt. Ghost: a custom integration's Admin API key; RankWhy creates a draft post through the Admin API with title, HTML, excerpt, meta title and meta description. Neither ever publishes.
Shopify, Webflow, Wix and others
Use the feed with Zapier or Make, or the webhook with your own glue; a direct Shopify or Webflow connector is not built yet.