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
POSTrequests and responds with a2xxstatus - 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-Signatureagainst 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
upsertByRankPineIdwitharticle.idas 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
refreshSitemapbefore 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:

| Field | What to enter |
|---|---|
| Endpoint URL | A public HTTPS URL, e.g. https://api.yoursite.com/hooks/rankpine — required |
| 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 Markdown | On by default — include the article body as Markdown |
| Include HTML | On 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.markdownis included only if Include Markdown is on;article.htmlonly if Include HTML is on.article.idis RankPine's stable article identifier.article.scheduledForis the original calendar time, ornullfor an unscheduled article. Usearticle.idfor idempotency; a schedule can change and is not an identity.featuredImageis the purpose-built 1200×630 cover/OG image, ornull.imagescontains inline body images in document order. Both use RankPine's CDN.- Inline images inside
html/markdownalready point at those same CDN URLs. article.languageis the language this article is written in.site.languageis your site's main language — on a multi-language site they differ.article.translationGroupIdisnullunless 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
| Header | Value |
|---|---|
Content-Type | application/json |
Accept | application/json |
X-RankPine-Event | article.published |
X-RankPine-Signature | sha256=<hex> — present only if you set a signing secret |
| your custom header | the 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.
Lovable via webhook
Bolt.new via webhook
Replit via webhook
Rocket.new via webhook
Base44 via webhook
Vercel v0 via webhook
Troubleshooting
Hashnode
Connect your Hashnode publication to RankPine with a personal access token. Find your publication ID, set up article publishing, and troubleshoot errors.
Lovable via webhook
Add a signed RankPine receiver to an existing Lovable project with an Edge Function, server-side secret, persistent article upsert, and sitemap refresh.