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
Use CaseEventsExample
Content Publishingpage.publishedAutomatically share new blog posts on social media
SEO Optimizationpage.updatedRegenerate sitemaps and notify search engines
Analytics Trackingpage.created, page.publishedTrack page creation and publishing rates
Backup Systemspage.updated, page.deletedBackup content changes to external storage
Team Notificationspage.publishedSend Slack/Discord notifications to your team
CI/CD Integrationpage.publishedTrigger deployments or cache invalidation

Supported Events

PageGun sends webhooks for these page lifecycle events:

EventDescriptionWhen It FiresPayload Includes
page.createdA new page is createdImmediately after page creationFull page data, status: "draft"
page.updatedA page is modifiedWhen page content or metadata changesUpdated page data
page.publishedA page goes liveWhen status changes from draft to publishedPublished page data with live URL
page.unpublishedA page is taken offlineWhen status changes from published to draftPage data with updated status
page.deletedA page is permanently removedImmediately after deletionBasic 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 32

Step 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

  1. PageGun creates an HMAC-SHA256 signature using your webhook secret
  2. The signature is sent in the X-PageGun-Signature header
  3. 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', 200

Go

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

  1. Respond quickly — Acknowledge webhooks within 30 seconds
  2. Verify signatures — Always check the X-PageGun-Signature header
  3. Process asynchronously — Don't block the webhook response with slow operations
  4. Handle duplicates — Use webhook IDs to prevent duplicate processing
  5. Log everything — Track webhook deliveries and processing status
  6. Use HTTPS — Only accept webhooks over secure connections
  7. Implement retries — Handle temporary failures gracefully

❌ Don'ts

  1. Don't process synchronously — Avoid slow operations in webhook handlers
  2. Don't ignore signatures — Unverified webhooks are a security risk
  3. Don't return 2xx for errors — Use appropriate status codes for failures
  4. Don't assume ordering — Process webhooks idempotently
  5. Don't hardcode secrets — Use environment variables
  6. 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:

  1. Set up your first webhook — Start with a simple notification
  2. Implement signature verification — Secure your endpoints
  3. Build automation workflows — Automate your content pipeline
  4. Monitor webhook performance — Ensure reliable delivery

Questions about webhooks? Join our Discord community where 500+ developers share webhook implementations and best practices.

© 2026 PageGun. All rights reserved.