RankPine docs
Integrations

Webhook / REST

Send each finished article as a JSON payload to any HTTPS endpoint you control — for custom systems, static sites, or any CMS RankPine doesn't natively support.

The webhook integration is the most flexible option: when an article is ready, RankPine sends it as a JSON payload via HTTP POST to an endpoint you control. Use it to push articles into a custom CMS, trigger a static-site rebuild, drop files in a repo, or anything else you can script.

This is the developer-oriented integration. If your blog runs on WordPress, Shopify, Odoo, Wix, Framer, Ghost, Webflow, or Notion, the dedicated guides for those are simpler. Reach for the webhook when nothing else fits.

What you'll need

  • A public HTTPS endpoint that accepts POST requests and responds with a 2xx status
  • A server-side signing secret. It is optional for the generic transport but required by the production receiver template below.
  • Persistent article storage and a public article route in your application.

Start with the shared receiver

Download or copy the dependency-free RankPine webhook receiver template. It uses the Web Request/Response APIs supported by Node, Deno, Edge Functions, and modern full-stack frameworks. You provide a small persistence adapter.

The reference implementation:

  • verifies X-RankPine-Signature against the exact raw body before parsing;
  • rejects invalid, stale, and future-dated requests outside a five-minute window;
  • validates both verification and publish payloads;
  • calls upsertByRankPineId with article.id as the idempotency key;
  • preserves HTML, Markdown, metadata, featured and inline images, and scheduledFor;
  • returns the stable external ID and canonical URL from your adapter; and
  • calls refreshSitemap before acknowledging a delivery.

Your application owns the sitemap

RankPine does not automatically update a custom site's sitemap. Implement the template's refreshSitemap adapter method against the sitemap, route cache, static rebuild, or discovery mechanism your application actually uses.

Connect in RankPine

Go to Integrations, click Connect on Webhook, and fill in the dialog:

The Connect a webhook dialog in RankPine

FieldWhat to enter
Endpoint URLA public HTTPS URL, e.g. https://api.yoursite.com/hooks/rankpinerequired
Signing secret(optional) Used to HMAC-sign each request so you can verify it came from RankPine
Auth header(optional) A custom header name to send, e.g. Authorization
Header value(optional) The value for that header, e.g. Bearer your-token
Include MarkdownOn by default — include the article body as Markdown
Include HTMLOn by default — include the article body as HTML

When you connect, RankPine sends a verification ping to your endpoint. It must respond 2xx or the connection won't save.

Verification ping

{
  "event": "verification",
  "sentAt": "2026-06-29T09:00:00.000Z"
}

Respond with any 2xx status to confirm the endpoint is reachable.

The publish payload

When an article publishes, RankPine sends a POST with this body:

{
  "event": "article.published",
  "publishedAt": "2026-06-29T09:00:00.000Z",
  "site": {
    "name": "Acme",
    "url": "https://acme.com",
    "language": "en"
  },
  "article": {
    "id": "1e2b3c4d-…",
    "title": "How to choose a CRM",
    "slug": "how-to-choose-a-crm",
    "metaDescription": "A practical guide to picking the right CRM…",
    "scheduledFor": "2026-06-29T09:00:00.000Z",
    "language": "en",
    "translationGroupId": null,
    "wordCount": 1840,
    "seoScore": 86,
    "sources": [{ "url": "https://example.com/study", "title": "Industry study" }],
    "markdown": "# How to choose a CRM\n\n…",
    "html": "<h1>How to choose a CRM</h1>…"
  },
  "featuredImage": {
    "url": "https://cdn.rankpine.com/…/hero.png",
    "alt": "How to choose a CRM"
  },
  "images": [{ "url": "https://cdn.rankpine.com/…/diagram.png", "alt": "CRM dashboard" }]
}

Notes:

  • article.markdown is included only if Include Markdown is on; article.html only if Include HTML is on.
  • article.id is RankPine's stable article identifier. article.scheduledFor is the original calendar time, or null for an unscheduled article. Use article.id for idempotency; a schedule can change and is not an identity.
  • featuredImage is the purpose-built 1200×630 cover/OG image, or null. images contains inline body images in document order. Both use RankPine's CDN.
  • Inline images inside html/markdown already point at those same CDN URLs.
  • article.language is the language this article is written in. site.language is your site's main language — on a multi-language site they differ.
  • article.translationGroupId is null unless the article has translations. See below.

Multi-language sites

If you've set additional languages, each version of an article is published separately — one POST per language, each with its own slug, its own language, and its own id/url in your response. There is no single request containing all of them.

Every version of the same article shares one translationGroupId, so you can tell which posts belong together and wire up hreflang yourself:

// three separate POSTs
{ "article": { "slug": "how-to-choose-a-crm",    "language": "en", "translationGroupId": "a1b2c3…" } }
{ "article": { "slug": "crm-auswahl",            "language": "de", "translationGroupId": "a1b2c3…" } }
{ "article": { "slug": "como-elegir-un-crm",     "language": "es", "translationGroupId": "a1b2c3…" } }

Storing translationGroupId pays off later

Keep it alongside each post. It's the only thing tying the versions together — the slugs differ by design, and translations can arrive minutes apart, so you can't rely on timing to group them.

The group id is the original article's own id, so the original arrives with translationGroupId equal to its own identity, and every translation carries the same value. A site with no additional languages always sends null.

Request headers

HeaderValue
Content-Typeapplication/json
Acceptapplication/json
X-RankPine-Eventarticle.published
X-RankPine-Signaturesha256=<hex> — present only if you set a signing secret
your custom headerthe auth header name/value you configured, if any

Your response

Respond 2xx to acknowledge. Optionally return JSON to tell RankPine where the article landed:

{ "id": "post_123", "url": "https://acme.com/blog/how-to-choose-a-crm" }

If you return an id and url, RankPine records them against the article. Keep both stable for the same article.id; this lets subsequent edits update the same external post. If you don't return them, a 2xx still acknowledges delivery, but RankPine can only record a generic fallback identity.

Verifying the signature

If you set a signing secret, every request includes an X-RankPine-Signature header. It's an HMAC-SHA256 of the raw request body, hex-encoded, prefixed with sha256=. Recompute it with your secret and compare — reject the request if it doesn't match.

The shared receiver uses Web Crypto's HMAC verification, which avoids a normal string comparison and works across Node and Edge runtimes. If you implement your own Node receiver, compare equal-length signature bytes with crypto.timingSafeEqual.

Hash the raw body

Compute the HMAC over the exact bytes you received, before any JSON parsing or re-serialisation — re-encoding can change the bytes and break the comparison.

Guided website-builder setup

Each guide below installs this same receiver contract in the builder's supported server runtime. All six still connect to Webhook / REST in RankPine.

Troubleshooting

On this page