RankPill Logo

Integrating with Webhooks

Webhooks allow you to connect RankPill to any website or service by sending real-time article data to a URL of your choice whenever you publish an article. This is perfect for connecting to platforms we don't natively support, using automation tools like Zapier or Make.com, or building custom integrations with your own code. You can receive article data including title, content (HTML and Markdown), images, meta descriptions, and more.

Common use cases include:

  • Custom-built CMS or static site generators (Next.js, Hugo, Jekyll)
  • PHP-based platforms (PrestaShop, Drupal, custom PHP sites)
  • Automation platforms (Zapier, Make.com, n8n)
  • Serverless functions (AWS Lambda, Vercel Functions, Cloudflare Workers)
  • Notification channels (Discord, Slack, Email)
  • Any service that can receive HTTP POST requests

Before you begin, you'll need a public URL endpoint that can receive POST requests with JSON data. This could be a webhook URL from Zapier, a serverless function you've deployed, or an API route in your application.

Setting Up Your Webhook

Navigate to Settings → Integrations in your RankPill dashboard and click on the Webhooks integration.

Configuration

  1. Enter Your Webhook URL - Paste the public URL of your endpoint that will receive the article data
  2. Choose Authentication Type:
    • Signature-based (Recommended) - Uses HMAC SHA-256 for cryptographic verification
    • Bearer Token - Simpler authentication with Bearer <token> format
  3. Connect and Save - RankPill will generate a unique Secret Key
  4. Copy Your Secret Key - Store it securely in your application's environment variables (e.g., RANKPILL_WEBHOOK_SECRET)
  5. Send a Test Payload - Click the “Send Test” button to verify your endpoint is working correctly

Once configured, RankPill will send a POST request to your webhook URL every time an article is published, containing all the article data in JSON format.

Testing Your Webhook

After connecting your webhook, you can send a test payload to verify everything is working:

  1. In the webhook settings, find the Test Your Webhook section
  2. Click Send Test to send a sample article payload to your endpoint
  3. Review the response - you'll see the status code and any response from your endpoint
  4. Check your endpoint logs to verify the test data was received correctly

The test payload includes a test: true field so you can identify and handle test requests differently in your code if needed.

Authentication

Every webhook request includes authentication headers to verify the request is genuinely from RankPill:

Signature-based Authentication (Default):

  • X-RankPill-Signature header with HMAC SHA-256 signature
  • Authorization header with sha256=<signature> format
  • Provides cryptographic verification that the request hasn't been tampered with

Bearer Token Authentication (Optional):

  • Authorization header with Bearer <your-secret-key>
  • Simpler to implement, ideal for Zapier and similar tools

Code Examples

Node.js – Signature Verification (Recommended)

// /pages/api/rankpill-webhook.js
import crypto from 'crypto';

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ message: 'Method Not Allowed' });
  }

  const rawBody = await getRawBody(req);
  const secret = process.env.RANKPILL_WEBHOOK_SECRET;

  // Verify signature from X-RankPill-Signature header
  const signature = req.headers['x-rankpill-signature'];
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');

  if (signature !== expectedSignature) {
    return res.status(401).json({ message: 'Invalid signature' });
  }

  // Process the article data
  const article = JSON.parse(rawBody);
  console.log('Received article:', article.title);

  // Handle test payloads
  if (article.test) {
    console.log('Test payload received - webhook is working!');
    return res.status(200).json({ message: 'Test webhook received successfully' });
  }

  // Add your custom logic here for real articles

  res.status(200).json({ message: 'Webhook received successfully' });
}

function getRawBody(req) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    req.on('data', chunk => chunks.push(chunk));
    req.on('end', () => resolve(Buffer.concat(chunks)));
    req.on('error', reject);
  });
}

Node.js – Bearer Token (Simpler)

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ message: 'Method Not Allowed' });
  }

  const authHeader = req.headers['authorization'];
  const expectedToken = `Bearer ${process.env.RANKPILL_WEBHOOK_SECRET}`;

  if (!authHeader || authHeader !== expectedToken) {
    return res.status(401).json({ message: 'Invalid authorization' });
  }

  const article = req.body;
  console.log('Received article:', article.title);

  if (article.test) {
    return res.status(200).json({ message: 'Test webhook received successfully' });
  }

  res.status(200).json({ message: 'Webhook received successfully' });
}

PHP – Signature Verification (Recommended)

<?php
$rawBody = file_get_contents('php://input');
$secret = getenv('RANKPILL_WEBHOOK_SECRET');

$signature = $_SERVER['HTTP_X_RANKPILL_SIGNATURE'] ?? '';
$expectedSignature = hash_hmac('sha256', $rawBody, $secret);

if ($signature !== $expectedSignature) {
    http_response_code(401);
    echo json_encode(['message' => 'Invalid signature']);
    exit;
}

$article = json_decode($rawBody, true);

if (isset($article['test']) && $article['test'] === true) {
    http_response_code(200);
    echo json_encode(['message' => 'Test webhook received successfully']);
    exit;
}

http_response_code(200);
echo json_encode(['message' => 'Webhook received successfully']);
?>

PHP – Bearer Token (Simpler)

<?php
$secret = getenv('RANKPILL_WEBHOOK_SECRET');

$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$expectedToken = 'Bearer ' . $secret;

if ($authHeader !== $expectedToken) {
    http_response_code(401);
    echo json_encode(['message' => 'Invalid authorization']);
    exit;
}

$rawBody = file_get_contents('php://input');
$article = json_decode($rawBody, true);

if (isset($article['test']) && $article['test'] === true) {
    http_response_code(200);
    echo json_encode(['message' => 'Test webhook received successfully']);
    exit;
}

http_response_code(200);
echo json_encode(['message' => 'Webhook received successfully']);
?>

Payload Structure

The webhook sends article data as JSON:

{
  "title": "How to Build Backlinks for Better SEO",
  "content_html": "<h1>How to Build Backlinks for Better SEO</h1><p>Building high-quality backlinks...</p>",
  "content_markdown": "# How to Build Backlinks for Better SEO\n\nBuilding high-quality backlinks...",
  "slug": "how-to-build-backlinks-for-better-seo",
  "meta_description": "Learn proven strategies to build high-quality backlinks...",
  "status": "published",
  "featured_image": "https://images.unsplash.com/photo-1516321318423-f06f85e504b3",
  "published_url": "https://yourblog.com/blog/how-to-build-backlinks-for-better-seo",
  "scheduled_date": null,
  "published_at": "2024-03-15T10:30:00Z",
  "is_republish": false,
  "test": false
}

Field Descriptions:

  • title - Article title
  • content_html - Full article content in HTML format
  • content_markdown - Full article content in Markdown format
  • slug - URL-friendly slug for the article (remains constant for updates)
  • meta_description - SEO meta description
  • status - Always “published” when webhook is triggered
  • featured_image - URL to the featured image (if any)
  • published_url - URL where the article was published (if available)
  • published_at - Timestamp when the webhook was sent
  • is_republish - true when updating an existing article, false for new articles
  • test - true for test payloads sent via “Send Test” button, false or omitted for real articles

Handling Article Updates

After publishing an article, you can click the “Update Article” button to push changes to your webhook endpoint. When this happens, RankPill sends the updated article data with is_republish: true.

The recommended pattern is to use upsert operations to avoid duplicate entries:

// Supabase example
await supabase
  .from('articles')
  .upsert({
    slug: article.slug,
    title: article.title,
    content_html: article.content_html,
  }, { onConflict: 'slug' });

// MongoDB example
await collection.updateOne(
  { slug: article.slug },
  { $set: articleData },
  { upsert: true }
);

// Prisma example
await prisma.article.upsert({
  where: { slug: article.slug },
  update: articleData,
  create: articleData
});

Important Notes:

  • The slug field remains constant across updates (it's locked after publishing)
  • Use the slug to identify which article to update in your database
  • The is_republish flag helps you track whether this is an update or initial publish
  • Always handle both cases to avoid duplicate article entries

Troubleshooting

Common webhook errors and how to debug them

Frequently Asked Questions

Quick answers about RankPill webhooks

For additional support, contact our team through the in-app support chat.

Ready to start publishing?

Finish the integration, then let RankPill publish SEO articles to your site every day on autopilot.