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
- Enter Your Webhook URL - Paste the public URL of your endpoint that will receive the article data
- Choose Authentication Type:
- Signature-based (Recommended) - Uses HMAC SHA-256 for cryptographic verification
- Bearer Token - Simpler authentication with
Bearer <token>format
- Connect and Save - RankPill will generate a unique Secret Key
- Copy Your Secret Key - Store it securely in your application's environment variables (e.g.,
RANKPILL_WEBHOOK_SECRET) - 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:
- In the webhook settings, find the Test Your Webhook section
- Click Send Test to send a sample article payload to your endpoint
- Review the response - you'll see the status code and any response from your endpoint
- 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-Signatureheader with HMAC SHA-256 signatureAuthorizationheader withsha256=<signature>format- Provides cryptographic verification that the request hasn't been tampered with
Bearer Token Authentication (Optional):
Authorizationheader withBearer <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 titlecontent_html- Full article content in HTML formatcontent_markdown- Full article content in Markdown formatslug- URL-friendly slug for the article (remains constant for updates)meta_description- SEO meta descriptionstatus- Always “published” when webhook is triggeredfeatured_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 sentis_republish- true when updating an existing article, false for new articlestest- 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
slugfield remains constant across updates (it's locked after publishing) - Use the
slugto identify which article to update in your database - The
is_republishflag 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.
Other integrations
Set up RankPill on another platform
WordPress
Install the WordPress plugin and start publishing articles to your WordPress blog.
Shopify
Connect your Shopify store and publish SEO articles to your Shopify blog.
Wix
Link your Wix website and automatically publish content to your Wix blog.
Webflow
Connect your Webflow CMS and auto-publish articles directly to your Webflow blog.
Ready to start publishing?
Finish the integration, then let RankPill publish SEO articles to your site every day on autopilot.
