Webhooks: Automate Your Workflow with Real-Time Events
Webhooks are HTTP callbacks that PageGun sends to your server when events happen in your project. Think of them as "push notifications" for your application—instead of constantly checking for changes, PageGun tells you immediately when something important occurs.
Why Use Webhooks?
Real-Time Automation
- Instant notifications when pages are published, updated, or deleted
- Automated workflows triggered by content changes
- Real-time synchronization with external systems
- Event-driven architecture for scalable applications
Popular Use Cases
| Use Case | Events | Example |
|---|---|---|
| Content Publishing | page.published | Automatically share new blog posts on social media |
| SEO Optimization | page.updated | Regenerate sitemaps and notify search engines |
| Analytics Tracking | page.created, page.published | Track page creation and publishing rates |
| Backup Systems | page.updated, page.deleted | Backup content changes to external storage |
| Team Notifications | page.published | Send Slack/Discord notifications to your team |
| CI/CD Integration | page.published | Trigger deployments or cache invalidation |
Supported Events
PageGun sends webhooks for these page lifecycle events:
| Event | Description | When It Fires | Payload Includes |
|---|---|---|---|
page.created | A new page is created | Immediately after page creation | Full page data, status: "draft" |
page.updated | A page is modified | When page content or metadata changes | Updated page data |
page.published | A page goes live | When status changes from draft to published | Published page data with live URL |
page.unpublished | A page is taken offline | When status changes from published to draft | Page data with updated status |
page.deleted | A page is permanently removed | Immediately after deletion | Basic page info (ID, name, slug) |
Setting Up Your First Webhook
Step 1: Create a Webhook Endpoint
First, create an endpoint on your server to receive webhook data:
// Express.js example
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.raw({ type: 'application/json' }));
app.post('/webhooks/pagegun', (req, res) => {
const signature = req.headers['x-pagegun-signature'];
const payload = req.body;
// Verify the webhook (see security section below)
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Unauthorized');
}
const event = JSON.parse(payload);
console.log('Received webhook:', event.event, event.data.page_name);
// Process the webhook asynchronously
processWebhookAsync(event);
// Respond quickly (PageGun expects 2xx within 30 seconds)
res.status(200).send('OK');
});
async function processWebhookAsync(event) {
switch (event.event) {
case 'page.published':
await notifyTeam(`New page published: ${event.data.page_name}`);
await updateSitemap(event.data);
break;
case 'page.updated':
await invalidateCache(event.data.slug);
break;
case 'page.deleted':
await removeFromSearch(event.data.id);
break;
}
}
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});Step 2: Configure Webhook in PageGun
Add your webhook endpoint to your project settings:
curl -X PUT "https://api.pagegun.com/projects/YOUR_PROJECT_ID/settings" \
-H "Authorization: Bearer $PAGEGUN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhooks": [
{
"url": "https://your-server.com/webhooks/pagegun",
"events": ["page.published", "page.updated", "page.deleted"],
"secret": "your_webhook_secret_here"
}
]
}'Important: Use a strong, random secret for webhook verification. Generate one with:
openssl rand -base64 32Step 3: Test Your Webhook
Test webhook delivery by publishing a page:
# Create and immediately publish a test page
curl -X POST "https://api.pagegun.com/pages" \
-H "Authorization: Bearer $PAGEGUN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"page_name": "Webhook Test",
"slug": "webhook-test",
"type": "article",
"project_id": "YOUR_PROJECT_ID",
"markdown_content": "# Testing Webhooks\n\nIf you receive this webhook, everything is working!"
}' \
| jq -r '.data.id' \
| xargs -I {} curl -X POST "https://api.pagegun.com/pages/{}/publish" \
-H "Authorization: Bearer $PAGEGUN_API_KEY"You should receive two webhooks: page.created and page.published.
Webhook Payload Format
Standard Payload Structure
Every webhook follows this format:
{
"event": "page.published",
"timestamp": "2024-01-21T09:30:45.123Z",
"project_id": "proj_abc123xyz",
"data": {
"id": "page_xyz789abc",
"page_name": "10 AI Tools Every Developer Should Know",
"slug": "ai-tools-developers",
"subroute": "blog",
"type": "article",
"status": "published",
"url": "https://myproject.pagegun.io/blog/ai-tools-developers",
"created_at": "2024-01-21T09:25:00.000Z",
"updated_at": "2024-01-21T09:30:45.000Z",
"published_at": "2024-01-21T09:30:45.123Z"
}
}Event-Specific Payloads
page.created
{
"event": "page.created",
"timestamp": "2024-01-21T09:25:00.123Z",
"data": {
"id": "page_xyz789abc",
"page_name": "Draft Article",
"slug": "draft-article",
"type": "article",
"status": "draft",
"created_at": "2024-01-21T09:25:00.123Z"
}
}page.updated
{
"event": "page.updated",
"timestamp": "2024-01-21T09:28:30.456Z",
"data": {
"id": "page_xyz789abc",
"page_name": "Updated Article Title",
"changes": ["page_name", "markdown_content"],
"updated_at": "2024-01-21T09:28:30.456Z"
}
}page.deleted
{
"event": "page.deleted",
"timestamp": "2024-01-21T09:35:15.789Z",
"data": {
"id": "page_xyz789abc",
"page_name": "Deleted Article",
"slug": "deleted-article",
"deleted_at": "2024-01-21T09:35:15.789Z"
}
}Security: Verifying Webhook Signatures
Always verify webhook signatures to ensure requests are actually from PageGun and haven't been tampered with.
How Signature Verification Works
- PageGun creates an HMAC-SHA256 signature using your webhook secret
- The signature is sent in the
X-PageGun-Signatureheader - Your server recreates the signature and compares it
Implementation Examples
Node.js/Express
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
// Use timing-safe comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(`sha256=${expectedSignature}`, 'utf8'),
Buffer.from(signature, 'utf8')
);
}
// Usage in Express middleware
app.post('/webhooks/pagegun', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-pagegun-signature'];
const payload = req.body;
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
console.log('Invalid webhook signature');
return res.status(401).send('Unauthorized');
}
// Process webhook...
res.status(200).send('OK');
});Python/Flask
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
def verify_webhook_signature(payload, signature, secret):
expected_signature = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
expected_sig_header = f"sha256={expected_signature}"
return hmac.compare_digest(expected_sig_header, signature)
@app.route('/webhooks/pagegun', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-PageGun-Signature')
payload = request.get_data()
if not verify_webhook_signature(payload, signature, os.environ['WEBHOOK_SECRET']):
abort(401)
event = request.get_json()
# Process webhook...
return 'OK', 200Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
)
func verifyWebhookSignature(payload []byte, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expectedSignature := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expectedSignature), []byte(signature))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
payload, _ := io.ReadAll(r.Body)
signature := r.Header.Get("X-PageGun-Signature")
if !verifyWebhookSignature(payload, signature, os.Getenv("WEBHOOK_SECRET")) {
http.Error(w, "Unauthorized", 401)
return
}
// Process webhook...
fmt.Fprint(w, "OK")
}Real-World Webhook Examples
Example 1: Social Media Automation
Automatically share new blog posts when they're published:
async function handlePagePublished(event) {
const page = event.data;
// Only share blog articles
if (page.type === 'article' && page.subroute === 'blog') {
const message = `📝 New blog post: ${page.page_name}\n\n${page.url}`;
// Share on multiple platforms
await Promise.all([
postToTwitter(message),
postToLinkedIn(message),
notifySlackChannel(message)
]);
console.log(`Shared article: ${page.page_name}`);
}
}
async function postToTwitter(message) {
// Twitter API integration
const response = await fetch('https://api.twitter.com/2/tweets', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.TWITTER_BEARER_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ text: message })
});
return response.json();
}Example 2: SEO Automation
Update sitemaps and notify search engines when content changes:
async function handleContentChange(event) {
switch (event.event) {
case 'page.published':
case 'page.updated':
case 'page.deleted':
await regenerateSitemap();
await notifySearchEngines();
break;
}
}
async function regenerateSitemap() {
// Get all published pages
const response = await fetch('https://api.pagegun.com/pages?status=published', {
headers: { 'Authorization': `Bearer ${process.env.PAGEGUN_API_KEY}` }
});
const pages = await response.json();
// Generate sitemap XML
const sitemap = generateSitemapXML(pages.data);
// Upload to your server or CDN
await uploadSitemap(sitemap);
console.log('Sitemap updated');
}
async function notifySearchEngines() {
const sitemapUrl = 'https://yoursite.com/sitemap.xml';
// Notify Google
await fetch(`https://www.google.com/ping?sitemap=${encodeURIComponent(sitemapUrl)}`);
// Notify Bing
await fetch(`https://www.bing.com/ping?sitemap=${encodeURIComponent(sitemapUrl)}`);
console.log('Search engines notified');
}Example 3: Content Backup System
Automatically backup content to external storage:
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
async function handlePageUpdate(event) {
const page = event.data;
// Backup the page content
await backupPageToS3(page);
// Keep audit trail
await logContentChange(event);
}
async function backupPageToS3(page) {
const backup = {
id: page.id,
page_name: page.page_name,
content: page.markdown_content || page.config,
backed_up_at: new Date().toISOString(),
url: page.url
};
const params = {
Bucket: 'pagegun-backups',
Key: `pages/${page.id}/${Date.now()}.json`,
Body: JSON.stringify(backup, null, 2),
ContentType: 'application/json'
};
await s3.upload(params).promise();
console.log(`Backed up page: ${page.page_name}`);
}Example 4: Team Notifications
Send notifications to Slack or Discord when content is published:
async function notifyTeam(event) {
const page = event.data;
const message = formatSlackMessage(event, page);
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(message)
});
}
function formatSlackMessage(event, page) {
const eventEmojis = {
'page.created': '📝',
'page.published': '🚀',
'page.updated': '✏️',
'page.deleted': '🗑️'
};
const emoji = eventEmojis[event.event] || '📄';
return {
text: `${emoji} Page ${event.event.split('.')[1]}`,
attachments: [{
color: event.event === 'page.published' ? 'good' : 'warning',
fields: [
{ title: 'Page', value: page.page_name, short: true },
{ title: 'Type', value: page.type, short: true },
{ title: 'URL', value: page.url || 'Draft', short: false }
],
footer: 'PageGun',
ts: Math.floor(Date.parse(event.timestamp) / 1000)
}]
};
}Multiple Webhooks Configuration
Configure different webhooks for different purposes:
curl -X PUT "https://api.pagegun.com/projects/YOUR_PROJECT_ID/settings" \
-H "Authorization: Bearer $PAGEGUN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhooks": [
{
"url": "https://api.yourapp.com/webhooks/pagegun/social",
"events": ["page.published"],
"secret": "social_webhook_secret"
},
{
"url": "https://api.yourapp.com/webhooks/pagegun/seo",
"events": ["page.published", "page.updated", "page.deleted"],
"secret": "seo_webhook_secret"
},
{
"url": "https://backup.yourapp.com/webhooks/pagegun",
"events": ["page.created", "page.updated", "page.deleted"],
"secret": "backup_webhook_secret"
}
]
}'Error Handling & Reliability
Webhook Delivery Guarantees
PageGun provides several reliability features:
- Retry logic — Failed webhooks are retried 3 times with exponential backoff
- Timeout handling — Webhooks that don't respond within 30 seconds are marked as failed
- Ordering — Webhooks for the same page are delivered in chronological order
- Duplicate prevention — Each webhook has a unique ID to prevent duplicate processing
Handling Webhook Failures
// Track webhook delivery attempts
const webhookAttempts = new Map();
app.post('/webhooks/pagegun', async (req, res) => {
const webhookId = req.headers['x-pagegun-webhook-id'];
const deliveryAttempt = parseInt(req.headers['x-pagegun-delivery-attempt']) || 1;
// Check for duplicates
if (webhookAttempts.has(webhookId)) {
console.log(`Duplicate webhook ${webhookId}, ignoring`);
return res.status(200).send('Already processed');
}
try {
// Process webhook
await processWebhook(JSON.parse(req.body));
// Mark as processed
webhookAttempts.set(webhookId, true);
res.status(200).send('OK');
} catch (error) {
console.error(`Webhook processing failed (attempt ${deliveryAttempt}):`, error);
// Return 5xx to trigger retry, 4xx to stop retrying
if (deliveryAttempt < 3) {
res.status(503).send('Temporary failure, please retry');
} else {
// After 3 attempts, log and accept to stop retries
console.error(`Webhook ${webhookId} failed after 3 attempts`);
res.status(200).send('Failed after retries');
}
}
});Webhook Queue Implementation
For high-volume applications, use a queue to process webhooks asynchronously:
const Bull = require('bull');
const webhookQueue = new Bull('webhook processing');
// Webhook endpoint - just queue the job
app.post('/webhooks/pagegun', (req, res) => {
const webhookData = JSON.parse(req.body);
// Queue for async processing
webhookQueue.add('process', webhookData, {
attempts: 3,
backoff: 'exponential',
delay: 1000
});
// Respond immediately
res.status(200).send('Queued');
});
// Process webhooks from queue
webhookQueue.process('process', async (job) => {
const event = job.data;
switch (event.event) {
case 'page.published':
await handlePagePublished(event);
break;
case 'page.updated':
await handlePageUpdated(event);
break;
// ... other events
}
});Testing Webhooks
Local Development with ngrok
Test webhooks locally using ngrok:
# 1. Start your local server
node webhook-server.js
# 2. Expose it with ngrok
ngrok http 3000
# 3. Use the ngrok URL in your webhook configuration
curl -X PUT "https://api.pagegun.com/projects/YOUR_PROJECT_ID/settings" \
-d '{"webhooks": [{"url": "https://abc123.ngrok.io/webhooks/pagegun", ...}]}'Webhook Testing Tool
Create a simple webhook inspector:
// webhook-inspector.js
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.raw({type: 'application/json'}));
app.post('/webhooks/:service', (req, res) => {
console.log('='.repeat(50));
console.log(`Webhook received for: ${req.params.service}`);
console.log(`Timestamp: ${new Date().toISOString()}`);
console.log(`Headers:`, req.headers);
console.log(`Body:`, req.body.toString());
console.log('='.repeat(50));
res.status(200).send('OK');
});
app.listen(3000, () => {
console.log('Webhook inspector listening on port 3000');
});Best Practices & Tips
✅ Do's
- Respond quickly — Acknowledge webhooks within 30 seconds
- Verify signatures — Always check the
X-PageGun-Signatureheader - Process asynchronously — Don't block the webhook response with slow operations
- Handle duplicates — Use webhook IDs to prevent duplicate processing
- Log everything — Track webhook deliveries and processing status
- Use HTTPS — Only accept webhooks over secure connections
- Implement retries — Handle temporary failures gracefully
❌ Don'ts
- Don't process synchronously — Avoid slow operations in webhook handlers
- Don't ignore signatures — Unverified webhooks are a security risk
- Don't return 2xx for errors — Use appropriate status codes for failures
- Don't assume ordering — Process webhooks idempotently
- Don't hardcode secrets — Use environment variables
- Don't forget error handling — Always catch and log exceptions
Performance Optimization
// Good: Fast response, async processing
app.post('/webhooks/pagegun', (req, res) => {
// Quick validation
if (!verifySignature(req)) {
return res.status(401).send('Unauthorized');
}
// Queue for background processing
processWebhookAsync(req.body);
// Respond immediately
res.status(200).send('OK');
});
// Bad: Slow synchronous processing
app.post('/webhooks/pagegun', async (req, res) => {
// This blocks the response for potentially seconds
await updateDatabase(req.body);
await sendNotifications(req.body);
await regenerateSitemap();
res.status(200).send('OK'); // Too late!
});Monitoring & Debugging
Webhook Delivery Logs
PageGun provides webhook delivery logs in your dashboard. Monitor these for:
- Failed deliveries — Check for consistent failures
- Response times — Ensure your server responds quickly
- Error patterns — Identify common issues
Health Check Endpoint
Add a health check for your webhook service:
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
webhooks_processed: webhookCounter.value
});
});Webhook Analytics
Track webhook performance:
const webhookMetrics = {
received: 0,
processed: 0,
failed: 0,
avg_processing_time: 0
};
app.post('/webhooks/pagegun', async (req, res) => {
const startTime = Date.now();
webhookMetrics.received++;
try {
await processWebhook(req.body);
webhookMetrics.processed++;
} catch (error) {
webhookMetrics.failed++;
console.error('Webhook failed:', error);
} finally {
// Update average processing time
const processingTime = Date.now() - startTime;
webhookMetrics.avg_processing_time =
(webhookMetrics.avg_processing_time + processingTime) / 2;
}
res.status(200).send('OK');
});
// Metrics endpoint
app.get('/webhook-metrics', (req, res) => {
res.json(webhookMetrics);
});Next Steps
Now that you understand PageGun webhooks:
- Set up your first webhook — Start with a simple notification
- Implement signature verification — Secure your endpoints
- Build automation workflows — Automate your content pipeline
- Monitor webhook performance — Ensure reliable delivery
Related Resources
- API Reference — Complete webhook configuration options
- AI Agents Guide — Use webhooks with AI automation
- Project Settings — Manage all project settings
Questions about webhooks? Join our Discord community where 500+ developers share webhook implementations and best practices.