Home
Back

Webhook Integration

How it works

When an article is published in RivalRank (manually or via auto-publish), RivalRank immediately sends a POST request to your configured endpoint with the full article payload. Your server receives it, does whatever it needs — save to a database, trigger a static rebuild, call revalidatePath() — and responds with a 200.

The request is authenticated with a Bearer token you generate inside the Integrations page. Delivery is fire-and-forget: a failed delivery does not block or undo the publish.

Setting up

  1. Go to Dashboard → Integrations
  2. In the Webhook card, enter your endpoint URL (must be HTTPS and publicly reachable)
  3. Click Save endpoint
  4. A secret token is auto-generated and shown once — copy it and store it as RIVALRANK_WEBHOOK_SECRET in your environment variables

If you need a new secret later, click Regenerate secret in the same card. Update your environment variable to match.

Verifying requests

Every request includes an Authorization header:

Authorization: Bearer <your-secret>

Always validate this before processing the payload. Reject requests that are missing or have a wrong token with a 401.

Payload

The request body is JSON with this shape:

{
  "event_type": "publish_article",
  "timestamp": "2026-04-29T08:00:00Z",
  "data": {
    "article": {
      "id": "abc123",
      "title": "Best Alternatives to Competitor X",
      "slug": "best-alternatives-to-competitor-x",
      "content_html": "<h1>...</h1>",
      "meta_description": "...",
      "created_at": "2026-04-28T10:00:00Z",
      "published_at": "2026-04-29T08:00:00Z",
      "published_url": "https://yoursite.com/best-alternatives...",
      "image_url": "https://cdn.rivalrankai.com/...",
      "tags": []
    }
  }
}
FieldTypeDescription
event_typestringAlways "publish_article"
timestampISO 8601When the publish completed
data.article.idstringRivalRank article ID
data.article.titlestringArticle title
data.article.slugstringURL-friendly slug
data.article.content_htmlstringFull article HTML
data.article.meta_descriptionstringSEO meta description
data.article.created_atISO 8601When the article was first created
data.article.published_atISO 8601When this publish happened
data.article.published_urlstringLive URL (WordPress or other), if available
data.article.image_urlstring | nullHero image URL, or null if none
data.article.tagsarrayTag strings (currently always empty, reserved for future use)

Next.js example

Create a route handler in your Next.js app. This example validates the token, saves the article, and revalidates the relevant path so the page is updated immediately.

// app/api/rivalrank-webhook/route.js
import { NextResponse } from "next/server";
import { revalidatePath } from "next/cache";

const TOKEN = process.env.RIVALRANK_WEBHOOK_SECRET;

export async function POST(req) {
  // 1. Verify the bearer token
  const auth = req.headers.get("authorization");
  if (!auth || auth !== `Bearer ${TOKEN}`) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const { event_type, data } = await req.json();

  if (event_type === "publish_article") {
    const { article } = data;

    // 2. Save to your database
    // await db.articles.upsert({ where: { id: article.id }, data: article });

    // 3. Revalidate the page so visitors see the new content immediately
    revalidatePath(`/blog/${article.slug}`);
    revalidatePath("/blog"); // revalidate the listing page too
  }

  return NextResponse.json({ ok: true });
}

Add your secret to .env.local (local) and your hosting environment (production):

RIVALRANK_WEBHOOK_SECRET=rrwh_your_secret_here

Plain Node.js / Express example

const express = require("express");
const app = express();

const TOKEN = process.env.RIVALRANK_WEBHOOK_SECRET;

app.use(express.json());

app.post("/webhooks/rivalrank", (req, res) => {
  const auth = req.headers["authorization"];
  if (!auth || auth !== `Bearer ${TOKEN}`) {
    return res.status(401).json({ error: "Unauthorized" });
  }

  const { event_type, data } = req.body;

  if (event_type === "publish_article") {
    const { article } = data;
    console.log("Published:", article.slug);
    // handle your logic here
  }

  res.status(200).json({ ok: true });
});

app.listen(3000);

Best practices

  • Always verify the bearer token before processing — reject with 401 if invalid
  • Respond quickly (within a few seconds). Offload slow work to a background queue if needed
  • Return 200 even if you choose to ignore a particular event — this prevents noise in server logs
  • Your endpoint must be publicly accessible over HTTPS. Local dev servers won't work — use a tunnelling tool like ngrok for testing
  • If you change your secret, update RIVALRANK_WEBHOOK_SECRET in your environment before saving the new secret in RivalRank

Troubleshooting

Endpoint not receiving requests

Confirm the URL is reachable from the internet (not localhost) and uses HTTPS. Check server logs and firewall rules.

401 errors

The token in RIVALRANK_WEBHOOK_SECRET must match exactly what is configured in Integrations. Check for trailing spaces or line breaks in your environment variable. Regenerate the secret if you are unsure.

500 errors from your server

Check your server logs for details. Common causes: JSON body not being parsed, unhandled promise rejections, or database errors.

Delivery is fire-and-forget

RivalRank does not retry failed deliveries. If your endpoint was down when an article was published, you can find the article data in the Articles section of your dashboard and re-publish from there.

Need help?

Contact us at hello@rivalrankai.com with your endpoint URL and the behaviour you're seeing.