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
For additional support, contact our team through the in-app support chat.
Troubleshooting.
403 Forbidden
- Your endpoint may have a firewall, WAF, or bot protection blocking the request
- Common with Cloudflare, AWS WAF, Azure Application Gateway, etc.
- RankPill sends standard HTTP headers including User-Agent, so this should work with most security tools
- If using Cloudflare: temporarily disable bot protection for your webhook path
- If using AWS WAF: add a rule to allow POST requests to your webhook endpoint
- Check your security tool's logs to see why the request is being blocked
- Consider whitelisting Vercel's IP ranges if you have IP-based restrictions
401 Unauthorized - Invalid Signature
- Ensure you're using the raw request body for signature calculation (not parsed JSON)
- Verify your secret key matches exactly what's shown in RankPill settings
- Check that your HMAC implementation uses SHA-256
- Make sure there are no extra characters or encoding issues with the secret
401 Unauthorized - Missing Authorization Header
- Your endpoint expects Bearer token format
- Change the Authentication Type to “Bearer Token” in RankPill webhook settings
Webhook Not Triggering
- Verify your webhook URL is publicly accessible
- Check that your endpoint returns a 200 status code
- Ensure your business has the webhook integration configured and connected
- Try publishing a test article to trigger the webhook
Connection Test Failed
- Make sure your endpoint accepts POST requests
- Verify the URL is correct and publicly accessible (not localhost)
- Check that your server/function is deployed and running
- Review your endpoint logs for errors
Frequently asked questions.
Can I use webhooks with Zapier?
Do I need to write code to use webhooks?
What happens if my webhook endpoint is down?
Can I update articles after publishing?
is_republish: true so you can update the existing article in your database instead of creating a duplicate.Can I have multiple webhook URLs?
Is the webhook data secure?
What's the difference between signature and bearer token authentication?
Can I test my webhook without publishing an article?
test: true flag so you can identify test requests in your code.Does the webhook include images?
featured_image URL and any images in the content are already embedded in the content_html field with their URLs.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.
Unlock Traffic on Autopilot
Grow organic traffic on autopilot. RankPill will research, write, optimize, and publish articles to your website daily.