← Back to Connections

Connect Express.js to PostPenguin

Easy SetupLanguage: Node.jsTime: 10 minutes

Express.js applications can easily receive PostPenguin webhooks. This guide works for any Node.js backend using Express.

🚀 Quick Setup

1. Install Dependencies

npm install express cors crypto

2. Create Webhook Handler

// server.js or routes/webhooks.js
const express = require('express')
const crypto = require('crypto')
const cors = require('cors')

const app = express()
const PORT = process.env.PORT || 3001

// Middleware
app.use(cors())
app.use(express.json())

// PostPenguin webhook endpoint
app.post('/api/webhooks/postpenguin', async (req, res) => {
  try {
    console.log('📨 Received webhook from PostPenguin')

    // Verify webhook signature (recommended)
    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

    // Validate required fields
    if (!title || !slug || !html) {
      return res.status(400).json({ error: 'Missing required fields: title, slug, html' })
    }

    // Save to your database
    const post = {
      id: crypto.randomUUID(),
      title,
      slug,
      html,
      metaTitle: meta_title || title,
      metaDescription: meta_description || '',
      featuredImage: featured_image || '',
      publishedAt: new Date().toISOString(),
    }

    // TODO: Save to your database (PostgreSQL, MongoDB, etc.)
    console.log('✅ Post received:', post.title)

    res.status(200).json({
      success: true,
      postId: post.id,
      message: 'Post received and saved'
    })

  } catch (error) {
    console.error('❌ Webhook processing error:', error)
    res.status(500).json({ error: 'Internal server error' })
  }
})

// API endpoint to fetch posts (for your frontend)
app.get('/api/posts', (req, res) => {
  try {
    const { status = 'publish', limit = 10, offset = 0, slug } = req.query

    // TODO: Fetch from your database
    let posts = [] // Replace with actual database query

    if (slug) {
      // Find single post by slug
      const post = posts.find(p => p.slug === slug)
      if (!post) {
        return res.status(404).json({ error: 'Post not found' })
      }
      return res.json({ post })
    }

    // Filter and paginate
    const filteredPosts = posts.slice(parseInt(offset), parseInt(offset) + parseInt(limit))

    res.json({
      posts: filteredPosts,
      pagination: {
        total: posts.length,
        limit: parseInt(limit),
        offset: parseInt(offset),
        has_more: parseInt(offset) + parseInt(limit) < posts.length
      }
    })

  } catch (error) {
    console.error('❌ Error fetching posts:', error)
    res.status(500).json({ error: 'Internal server error' })
  }
})

app.listen(PORT, () => {
  console.log(`🚀 Server running on port ${PORT}`)
  console.log(`📡 Webhook: http://localhost:${PORT}/api/webhooks/postpenguin`)
  console.log(`📖 Posts API: http://localhost:${PORT}/api/posts`)
})

3. Environment Variables

# .env
PORT=3001
POSTPENGUIN_WEBHOOK_SECRET=your-webhook-secret-here

4. Configure PostPenguin

When adding your site to PostPenguin:

  • Webhook URL: https://your-domain.com/api/webhooks/postpenguin
  • Secret Key: Same as POSTPENGUIN_WEBHOOK_SECRET

💾 Database Integration

See the full Express.js guide in our connections documentation for complete database examples including PostgreSQL, MongoDB, and MySQL.

Need Help?

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