โ† Back to Connections

Connect PHP to PostPenguin

Easy SetupLanguage: PHPTime: 10 minutes

Any PHP website can receive PostPenguin webhooks. This guide shows you how to create a simple webhook endpoint that saves posts to your database.

Perfect for: Custom PHP sites, Laravel, Symfony, CodeIgniter, or any PHP-based CMS.

๐Ÿš€ Quick Setup

Step 1: Create Webhook Endpoint

Create a new PHP file at /api/webhooks/postpenguin.php (or any accessible path):

<?php
// PostPenguin Webhook Handler
// Place this file at: /api/webhooks/postpenguin.php

header('Content-Type: application/json');

// Your webhook secret (set this to match PostPenguin)
$webhookSecret = getenv('POSTPENGUIN_WEBHOOK_SECRET') ?: 'your-secret-key';

// Get raw POST data
$rawData = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_POSTPENGUIN_SIGNATURE'] ?? '';

// Validate request
if (empty($rawData)) {
    http_response_code(400);
    echo json_encode(['error' => 'No data received']);
    exit;
}

// Verify signature (recommended)
if (!empty($signature)) {
    $expectedSignature = hash_hmac('sha256', $rawData, $webhookSecret);
    $receivedSignature = str_replace('sha256=', '', $signature);
    
    if (!hash_equals($expectedSignature, $receivedSignature)) {
        http_response_code(401);
        echo json_encode(['error' => 'Invalid signature']);
        exit;
    }
}

// Parse JSON data
$data = json_decode($rawData, true);

if (json_last_error() !== JSON_ERROR_NONE) {
    http_response_code(400);
    echo json_encode(['error' => 'Invalid JSON']);
    exit;
}

// Validate required fields
$requiredFields = ['title', 'slug', 'html'];
foreach ($requiredFields as $field) {
    if (empty($data[$field])) {
        http_response_code(400);
        echo json_encode(['error' => "Missing required field: $field"]);
        exit;
    }
}

// Extract post data
$post = [
    'postpenguin_id' => $data['postPenguinId'] ?? uniqid('pp_'),
    'title' => htmlspecialchars($data['title'], ENT_QUOTES, 'UTF-8'),
    'slug' => preg_replace('/[^a-z0-9-]/', '', strtolower($data['slug'])),
    'html' => $data['html'], // Sanitize as needed for your use case
    'meta_title' => htmlspecialchars($data['meta_title'] ?? $data['title'], ENT_QUOTES, 'UTF-8'),
    'meta_description' => htmlspecialchars($data['meta_description'] ?? '', ENT_QUOTES, 'UTF-8'),
    'featured_image' => filter_var($data['featured_image'] ?? '', FILTER_VALIDATE_URL) ?: null,
    'published_at' => date('Y-m-d H:i:s'),
];

// TODO: Save to your database (example below)
// $postId = saveToDatabase($post);

// For demo: just log and return success
error_log("PostPenguin webhook received: " . $post['title']);

http_response_code(200);
echo json_encode([
    'success' => true,
    'postId' => $post['postpenguin_id'],
    'message' => 'Post received successfully'
]);

Step 2: Add Database Storage

Add this function to save posts to MySQL/PostgreSQL:

<?php
// Database configuration
$dbHost = getenv('DB_HOST') ?: 'localhost';
$dbName = getenv('DB_NAME') ?: 'your_database';
$dbUser = getenv('DB_USER') ?: 'your_username';
$dbPass = getenv('DB_PASS') ?: 'your_password';

function saveToDatabase($post) {
    global $dbHost, $dbName, $dbUser, $dbPass;
    
    try {
        $pdo = new PDO(
            "mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4",
            $dbUser,
            $dbPass,
            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
        );
        
        // Check if post exists
        $stmt = $pdo->prepare("SELECT id FROM posts WHERE postpenguin_id = ?");
        $stmt->execute([$post['postpenguin_id']]);
        $existing = $stmt->fetch();
        
        if ($existing) {
            // Update existing post
            $stmt = $pdo->prepare("
                UPDATE posts SET
                    title = ?,
                    slug = ?,
                    html = ?,
                    meta_title = ?,
                    meta_description = ?,
                    featured_image = ?,
                    updated_at = NOW()
                WHERE postpenguin_id = ?
            ");
            $stmt->execute([
                $post['title'],
                $post['slug'],
                $post['html'],
                $post['meta_title'],
                $post['meta_description'],
                $post['featured_image'],
                $post['postpenguin_id']
            ]);
            return $existing['id'];
        } else {
            // Insert new post
            $stmt = $pdo->prepare("
                INSERT INTO posts 
                (postpenguin_id, title, slug, html, meta_title, meta_description, featured_image, published_at, created_at)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())
            ");
            $stmt->execute([
                $post['postpenguin_id'],
                $post['title'],
                $post['slug'],
                $post['html'],
                $post['meta_title'],
                $post['meta_description'],
                $post['featured_image'],
                $post['published_at']
            ]);
            return $pdo->lastInsertId();
        }
    } catch (PDOException $e) {
        error_log("Database error: " . $e->getMessage());
        throw new Exception("Database error");
    }
}

Step 3: Create Database Table

Run this SQL to create the posts table:

CREATE TABLE posts (
    id INT AUTO_INCREMENT PRIMARY KEY,
    postpenguin_id VARCHAR(255) UNIQUE NOT NULL,
    title VARCHAR(500) NOT NULL,
    slug VARCHAR(255) NOT NULL,
    html TEXT NOT NULL,
    meta_title VARCHAR(500),
    meta_description TEXT,
    featured_image VARCHAR(1000),
    published_at DATETIME,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    INDEX idx_slug (slug),
    INDEX idx_published_at (published_at)
);

Step 4: Configure PostPenguin

When adding your site to PostPenguin:

  • Webhook URL: https://your-site.com/api/webhooks/postpenguin.php
  • Secret Key: Set a secure random string (same as POSTPENGUIN_WEBHOOK_SECRET)

๐Ÿ“– Display Posts on Your Site

Create a simple blog page to display your posts:

<?php
// blog.php - Display posts from database

$pdo = new PDO("mysql:host=localhost;dbname=your_db", "user", "pass");

// Fetch published posts
$stmt = $pdo->query("
    SELECT * FROM posts 
    ORDER BY published_at DESC 
    LIMIT 10
");
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>

<!DOCTYPE html>
<html>
<head>
    <title>Blog</title>
</head>
<body>
    <h1>Latest Posts</h1>
    
    <?php foreach ($posts as $post): ?>
    <article>
        <?php if ($post['featured_image']): ?>
        <img src="<?= htmlspecialchars($post['featured_image']) ?>" alt="<?= htmlspecialchars($post['title']) ?>">
        <?php endif; ?>
        
        <h2 className="text-gray-900">
            <a href="/post/<?= htmlspecialchars($post['slug']) ?>">
                <?= htmlspecialchars($post['title']) ?>
            </a>
        </h2>
        
        <p className="text-gray-700"><?= htmlspecialchars($post['meta_description']) ?></p>
        
        <time><?= date('F j, Y', strtotime($post['published_at'])) ?></time>
    </article>
    <?php endforeach; ?>
</body>
</html>

๐Ÿงช Testing

Test Your Webhook

# Test with curl
curl -X POST https://your-site.com/api/webhooks/postpenguin.php \
  -H "Content-Type: application/json" \
  -d '{
    "postPenguinId": "test_123",
    "title": "Test Post",
    "slug": "test-post",
    "html": "<p className="text-gray-700">This is a test post from PostPenguin.</p>",
    "meta_title": "Test Post Title",
    "meta_description": "Testing PHP webhook integration",
    "featured_image": "https://via.placeholder.com/800x400",
    "status": "publish"
  }'

Test with Signature

# Generate signature and test
SECRET="your-webhook-secret"
PAYLOAD='{"title":"Test","slug":"test","html":"<p className="text-gray-700">Test</p>"}'
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)

curl -X POST https://your-site.com/api/webhooks/postpenguin.php \
  -H "Content-Type: application/json" \
  -H "X-PostPenguin-Signature: sha256=$SIGNATURE" \
  -d "$PAYLOAD"

๐Ÿ”’ Security Checklist

  • Always verify webhook signatures with HMAC-SHA256
  • Use HTTPS for your webhook endpoint
  • Store secrets in environment variables, not in code
  • Use prepared statements to prevent SQL injection
  • Sanitize HTML content before displaying

๐Ÿ› Troubleshooting

Webhook Not Receiving Posts

  1. Check your webhook URL is publicly accessible
  2. Verify the URL doesn't have typos in PostPenguin settings
  3. Check PHP error logs: tail -f /var/log/php/error.log
  4. Ensure your server allows POST requests to the endpoint

Signature Verification Failing

  1. Ensure your secret key matches PostPenguin's configuration
  2. Check that you're using the raw request body for signature calculation
  3. Verify the signature header name is correct: X-PostPenguin-Signature

Database Errors

  1. Check database credentials are correct
  2. Ensure the posts table exists with correct schema
  3. Verify database user has INSERT/UPDATE permissions

Need Help?

Check our webhook documentation for technical details, or contact support for custom integrations.