Webhook - Setting up your API/Webhook Integration
Written By Founder@SEOProAI
Last updated 6 months ago
JavaScript Webhook Integration Guide
This guide will help you integrate SEOPro AI's webhook system with any JavaScript application (Node.js, Express, Next.js, React, etc.) to automatically receive blog post notifications.
Overview
When SEOPro AI publishes a blog post, it will send a webhook notification to your application. Your app will receive the blog data and can automatically process or store the articles as needed.
Setting Up Your Webhook
To set up a webhook integration, you'll need to provide the following information:
Integration Name: A unique name for your webhook integration (2-30 characters, no special characters)
Webhook Endpoint: The URL where you want to receive webhook events (must be a valid HTTPS URL)
Access Token: A secret key used to verify the authenticity of incoming webhook requests
Implementing Webhook Authentication
To ensure the security of your webhook, we use a Bearer token to authenticate incoming requests. Here's how to verify incoming webhooks:
Example in Node.js/Express:
const express = require('express');
const app = express();
// This should be securely stored in environment variables
const ACCESS_TOKEN = process.env.SEOPRO_WEBHOOK_TOKEN;
function validateAccessToken(req) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return false;
}
const token = authHeader.split(" ")[1];
return token === ACCESS_TOKEN;
}
app.use(express.json());
app.post('/webhook', (req, res) => {
// Validate access token
if (!validateAccessToken(req)) {
return res.status(401).json({ error: 'Invalid access token' });
}
// Validate event type
if (req.body.event_type !== 'publish_articles') {
return res.status(400).json({ error: 'Unsupported event type' });
}
// Process the webhook payload
const { articles } = req.body.data;
articles.forEach(article => {
console.log(`Received article: ${article.title}`);
// Process each article (save to database, file system, etc.)
processArticle(article);
});
res.status(200).json({ message: 'Webhook processed successfully' });
});
async function processArticle(article) {
// Implement your article processing logic here
// Examples:
// - Save to database
// - Write to file system
// - Send to CMS
// - Trigger other workflows
console.log('Processing article:', {
id: article.id,
title: article.title,
slug: article.slug
});
}
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
Example in Next.js API Routes:
// pages/api/webhook.js (Pages Router)
// or app/api/webhook/route.js (App Router)
export default function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
// Validate access token
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing authorization header' });
}
const token = authHeader.replace('Bearer ', '');
if (token !== process.env.SEOPRO_WEBHOOK_TOKEN) {
return res.status(401).json({ error: 'Invalid access token' });
}
// Validate event type
if (req.body.event_type !== 'publish_articles') {
return res.status(400).json({ error: 'Unsupported event type' });
}
// Process articles
const { articles } = req.body.data;
articles.forEach(article => {
// Process each article
processArticle(article);
});
res.status(200).json({ message: 'Webhook processed successfully' });
}
function processArticle(article) {
// Your processing logic here
console.log(`Processing: ${article.title}`);
}
Example with Fastify:
const fastify = require('fastify')({ logger: true });
const ACCESS_TOKEN = process.env.SEOPRO_WEBHOOK_TOKEN;
// Register JSON parser
fastify.register(require('@fastify/formbody'));
fastify.post('/webhook', async (request, reply) => {
// Validate access token
const authHeader = request.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return reply.status(401).send({ error: 'Invalid authorization header' });
}
const token = authHeader.replace('Bearer ', '');
if (token !== ACCESS_TOKEN) {
return reply.status(401).send({ error: 'Invalid access token' });
}
// Process webhook
const { event_type, data } = request.body;
if (event_type !== 'publish_articles') {
return reply.status(400).send({ error: 'Unsupported event type' });
}
// Process articles
for (const article of data.articles) {
await processArticle(article);
}
return { message: 'Webhook processed successfully' };
});
async function processArticle(article) {
// Your processing logic
console.log(`Processing: ${article.title}`);
}
const start = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();
Publish Articles Event
When an article is published, we'll send a POST request to your webhook endpoint with a JSON payload. The payload will include:
event_type: The type of event that occurred (always "publish_articles" in this case)
timestamp: The time the event occurred (ISO 8601 format)
data: An object containing event-specific data, including an array of affected articles
Article Data Properties
Each article in the payload will contain the following properties:
id: Unique identifier for the article
title: The title of the article
content_markdown: The article content in Markdown format
content_html: The article content in HTML format
meta_description: A brief description of the article for SEO purposes
created_at: The timestamp when the article was created (ISO 8601 format)
image_url: The URL of the main image associated with the article
slug: The URL-friendly version of the article title used for permalinks
tags: An array of tags associated with the article
Example Payload
{
"event_type": "publish_articles",
"timestamp": "2023-04-01T12:00:00Z",
"data": {
"articles": [
{
"id": "123456",
"title": "How to Implement Webhooks",
"content_markdown": "Webhooks are a powerful...",
"content_html": "<p>Webhooks are a powerful...</p>",
"meta_description": "Learn how to implement webhooks in your application",
"created_at": "2023-03-31T10:30:00Z",
"image_url": "https://example.com/images/webhook-article.jpg",
"slug": "how-to-implement-webhooks",
"tags": ["webhooks", "integration", "api"]
},
{
"id": "789012",
"title": "Best Practices for API Design",
"content_markdown": "When designing an API...",
"content_html": "<p>When designing an API...</p>",
"meta_description": "Discover the best practices for designing robust APIs",
"created_at": "2023-03-31T11:45:00Z",
"image_url": "https://example.com/images/api-design-article.jpg",
"slug": "best-practices-for-api-design",
"tags": ["api", "design", "best practices"]
}
]
}
}
Article Processing Examples
Save to Database (MongoDB)
const { MongoClient } = require('mongodb');
async function processArticle(article) {
const client = new MongoClient(process.env.MONGODB_URI);
try {
await client.connect();
const db = client.db('blog');
const collection = db.collection('articles');
// Upsert article (update if exists, insert if new)
await collection.replaceOne(
{ seoProId: article.id },
{
seoProId: article.id,
title: article.title,
content: article.content_html,
contentMarkdown: article.content_markdown,
metaDescription: article.meta_description,
slug: article.slug,
featuredImage: article.image_url,
tags: article.tags,
publishedAt: new Date(article.created_at),
createdAt: new Date()
},
{ upsert: true }
);
console.log(`Article saved: ${article.title}`);
} finally {
await client.close();
}
}
Save to File System (Markdown)
const fs = require('fs').promises;
const path = require('path');
async function processArticle(article) {
const frontmatter = `---
title: "${article.title.replace(/"/g, '\\"')}"
description: "${article.meta_description?.replace(/"/g, '\\"') || ''}"
publishedAt: "${article.created_at}"
image: "${article.image_url || ''}"
tags: [${article.tags.map(tag => `"${tag}"`).join(', ')}]
seoProId: "${article.id}"
---
`;
const content = frontmatter + article.content_markdown;
const blogDir = path.join(process.cwd(), 'content/blog');
const filePath = path.join(blogDir, `${article.slug}.md`);
// Ensure directory exists
await fs.mkdir(blogDir, { recursive: true });
// Write the file
await fs.writeFile(filePath, content, 'utf8');
console.log(`Article saved to: ${filePath}`);
}
Send to WordPress REST API
const axios = require('axios');
async function processArticle(article) {
const wordpressUrl = process.env.WORDPRESS_URL;
const username = process.env.WORDPRESS_USERNAME;
const password = process.env.WORDPRESS_APP_PASSWORD;
const auth = Buffer.from(`${username}:${password}`).toString('base64');
try {
const response = await axios.post(`${wordpressUrl}/wp-json/wp/v2/posts`, {
title: article.title,
content: article.content_html,
excerpt: article.meta_description,
slug: article.slug,
status: 'publish',
meta: {
seopro_id: article.id
}
}, {
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json'
}
});
console.log(`Article published to WordPress: ${response.data.link}`);
} catch (error) {
console.error('Error publishing to WordPress:', error.message);
}
}
Environment Variables
Create a .env file in your project root:
# Webhook Configuration
SEOPRO_WEBHOOK_TOKEN=your_generated_access_token_here
# Database (if using MongoDB)
MONGODB_URI=mongodb://localhost:27017/blog
# WordPress (if using WordPress integration)
WORDPRESS_URL=https://yoursite.com
WORDPRESS_USERNAME=your_username
WORDPRESS_APP_PASSWORD=your_app_password
Testing Your Webhook
1. Local Testing with ngrok
For local development, use ngrok to expose your local server:
# Install ngrok
npm install -g ngrok
# Start your webhook server
node webhook-server.js
# In another terminal, expose port 3000
ngrok http 3000
Use the ngrok HTTPS URL as your webhook endpoint in SEOPro AI.
2. Test Script
Create a test script to verify your webhook endpoint:
// test-webhook.js
const axios = require('axios');
const testPayload = {
event_type: "publish_articles",
timestamp: new Date().toISOString(),
data: {
articles: [
{
id: "test-123",
title: "Test Article from SEOPro AI",
content_markdown: "# Test Article\n\nThis is a test article.",
content_html: "<h1>Test Article</h1><p>This is a test article.</p>",
meta_description: "Test article description",
created_at: new Date().toISOString(),
image_url: "https://example.com/test-image.jpg",
slug: "test-article-seopro-ai",
tags: ["test", "webhook"]
}
]
}
};
async function testWebhook() {
try {
const response = await axios.post('http://localhost:3000/webhook', testPayload, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.SEOPRO_WEBHOOK_TOKEN}`
}
});
console.log('β
Webhook test successful:', response.data);
} catch (error) {
console.error('β Webhook test failed:', error.response?.data || error.message);
}
}
testWebhook();
3. Production Testing
Once you've set up your webhook integration in SEOPro AI:
Navigate to any article page in your dashboard
Click the "Publish" button to publish the article
Your webhook endpoint should receive a POST request with the article data
Check your server logs to verify the webhook was received and processed correctly
Security Best Practices
Always use HTTPS for your webhook endpoint in production
Validate the Bearer token on every request
Store your access token securely in environment variables
Implement rate limiting to prevent abuse
Log webhook events for debugging and monitoring
Validate payload structure before processing
Error Handling
app.post('/webhook', async (req, res) => {
try {
// Validation logic here...
const { articles } = req.body.data;
const results = [];
for (const article of articles) {
try {
await processArticle(article);
results.push({ id: article.id, status: 'success' });
} catch (error) {
console.error(`Failed to process article ${article.id}:`, error);
results.push({ id: article.id, status: 'error', error: error.message });
}
}
res.status(200).json({
message: 'Webhook processed',
results
});
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
Troubleshooting
Common Issues
401 Unauthorized: Check your access token in both SEOPro AI dashboard and environment variables
Webhook not received: Verify your endpoint URL is correct and publicly accessible
SSL/TLS errors: Ensure your webhook endpoint uses valid HTTPS certificate
Timeout errors: Make sure your webhook responds within 30 seconds
Debug Logging
Add comprehensive logging to help with troubleshooting:
app.post('/webhook', (req, res) => {
console.log('Webhook received:', {
timestamp: new Date().toISOString(),
headers: req.headers,
body: req.body,
ip: req.ip
});
// Your processing logic...
});
This guide provides a solid foundation for integrating SEOPro AI webhooks with any JavaScript application. Choose the framework-specific example that best matches your setup and customize the article processing logic to fit your needs.