Webhook Documentation

Post Penguin can automatically send posts to your webhook endpoint when they're approved.

How Webhooks Work

  1. Configure your webhook endpoint URL in your site settings
  2. Post Penguin generates a blog post
  3. You review and approve the post
  4. Post Penguin automatically sends the post to your webhook endpoint
  5. 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/json
  • X-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 OK status 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:

  1. Set WEBHOOK_SECRET in Post Penguin's environment variables
  2. Set the same secret in your application's environment
  3. 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.