Webhook Documentation
Post Penguin can automatically send posts to your webhook endpoint when they're approved.
How Webhooks Work
- Configure your webhook endpoint URL in your site settings
- Post Penguin generates a blog post
- You review and approve the post
- Post Penguin automatically sends the post to your webhook endpoint
- Your endpoint receives the post and saves it to your CMS/database
Webhook Payload
When a post is approved, Post Penguin sends a POST request to your webhook URL with the following JSON payload:
{
"title": "Post Title",
"slug": "post-slug",
"html": "<p>Post content...</p>",
"meta_title": "SEO Title",
"meta_description": "SEO description",
"featured_image": "https://image-url.com/image.jpg",
"status": "publish"
}Request Headers
Content-Type: application/jsonX-PostPenguin-Signature: sha256=<signature>(if signature verification is enabled)
Code Examples
Node.js/Express
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
app.post('/api/posts/webhook', (req, res) => {
// Optional: Verify webhook signature
const signature = req.headers['x-postpenguin-signature'];
const webhookSecret = process.env.POSTPENGUIN_WEBHOOK_SECRET;
if (webhookSecret && signature) {
const payloadString = JSON.stringify(req.body);
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(payloadString)
.digest('hex');
const receivedSignature = signature.replace('sha256=', '');
if (receivedSignature !== expectedSignature) {
return res.status(401).json({ error: 'Invalid signature' });
}
}
const { title, slug, html, meta_title, meta_description, featured_image } = req.body;
// Save to your database/CMS
// ... your save logic here ...
res.status(200).json({ success: true });
});
app.listen(3000);PHP
<?php
header('Content-Type: application/json');
$data = json_decode(file_get_contents('php://input'), true);
$signature = $_SERVER['HTTP_X_POSTPENGUIN_SIGNATURE'] ?? '';
// Optional: Verify webhook signature
$webhookSecret = getenv('POSTPENGUIN_WEBHOOK_SECRET');
if ($webhookSecret && $signature) {
$payloadString = file_get_contents('php://input');
$expectedSignature = hash_hmac('sha256', $payloadString, $webhookSecret);
$receivedSignature = str_replace('sha256=', '', $signature);
if ($receivedSignature !== $expectedSignature) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
}
$title = $data['title'];
$slug = $data['slug'];
$html = $data['html'];
$metaTitle = $data['meta_title'];
$metaDescription = $data['meta_description'];
$featuredImage = $data['featured_image'];
// Save to your database
// ... your save logic here ...
http_response_code(200);
echo json_encode(['success' => true]);
?>Python/Flask
from flask import Flask, request, jsonify
import hmac
import hashlib
import os
app = Flask(__name__)
@app.route('/api/posts/webhook', methods=['POST'])
def webhook():
# Optional: Verify webhook signature
signature = request.headers.get('X-PostPenguin-Signature', '')
webhook_secret = os.getenv('POSTPENGUIN_WEBHOOK_SECRET')
if webhook_secret and signature:
payload_string = request.get_data(as_text=True)
expected_signature = hmac.new(
webhook_secret.encode(),
payload_string.encode(),
hashlib.sha256
).hexdigest()
received_signature = signature.replace('sha256=', '')
if received_signature != expected_signature:
return jsonify({'error': 'Invalid signature'}), 401
data = request.json
title = data['title']
slug = data['slug']
html = data['html']
# ... save to database ...
return jsonify({'success': True}), 200
if __name__ == '__main__':
app.run()Response Requirements
Your webhook endpoint should:
- Return a
200 OKstatus code on success - Return a JSON response (optional, but recommended)
- Respond within 30 seconds (timeout limit)
- Handle errors gracefully and return appropriate status codes
Security
We recommend verifying webhook signatures to ensure requests are coming from Post Penguin:
- Set
WEBHOOK_SECRETin Post Penguin's environment variables - Set the same secret in your application's environment
- Verify the signature in your webhook handler (see examples above)
Alternative: Use the API
Instead of webhooks, you can also poll the Post Penguin API to fetch posts on demand. This gives you more control over when and how posts are retrieved.
See API documentation for details.