ghost-webhooks

Installation
SKILL.md

Ghost Webhooks & Integrations

Overview

Ghost webhooks enable event-driven integrations by sending HTTP POST requests to configured URLs when specific events occur. Combined with the Content and Admin API SDKs, webhooks form the foundation for custom integrations, static site generation triggers, notification systems, and content automation pipelines.

When to Use

  • Setting up webhooks to react to Ghost events (publishing, member signup, etc.)
  • Building custom integrations with Ghost
  • Triggering static site rebuilds on content changes
  • Sending notifications (Slack, Discord, email) on Ghost events
  • Automating content workflows (cross-posting, syndication)
  • Building headless CMS frontends with Ghost as the backend
  • Migrating content to or from Ghost

Webhook Events

Ghost supports 31 webhook events across 5 categories:

Site Events

Event Trigger
site.changed Any content or settings change

Post Events (10)

Event Trigger
post.added Post created
post.deleted Post deleted
post.edited Post edited
post.published Post published
post.published.edited Published post edited
post.unpublished Post unpublished (reverted to draft)
post.scheduled Post scheduled for future publication
post.unscheduled Scheduled post unscheduled
post.rescheduled Scheduled post rescheduled

Page Events (10)

Event Trigger
page.added Page created
page.deleted Page deleted
page.edited Page edited
page.published Page published
page.published.edited Published page edited
page.unpublished Page unpublished
page.scheduled Page scheduled
page.unscheduled Page unscheduled
page.rescheduled Page rescheduled

Tag Events (7)

Event Trigger
tag.added Tag created
tag.edited Tag edited
tag.deleted Tag deleted
post.tag.attached Tag added to a post
post.tag.detached Tag removed from a post
page.tag.attached Tag added to a page
page.tag.detached Tag removed from a page

Member Events (3)

Event Trigger
member.added New member registered
member.edited Member profile updated
member.deleted Member deleted

Creating Webhooks

Via Ghost Admin UI

Ghost Admin > Settings > Integrations > Custom Integration > Add Webhook

Select the event and provide the target URL.

Via Admin API

import GhostAdminAPI from '@tryghost/admin-api';

const api = new GhostAdminAPI({
  url: 'https://my-ghost-site.com',
  key: '{id}:{secret}',
  version: 'v5.0'
});

// Create a webhook
const webhook = await api.webhooks.add({
  event: 'post.published',
  target_url: 'https://my-app.com/webhooks/ghost',
  name: 'Rebuild site on publish',
  integration_id: 'your-integration-id'
});

// Update a webhook
await api.webhooks.edit({
  id: webhook.id,
  target_url: 'https://new-url.com/webhook'
});

// Delete a webhook
await api.webhooks.delete({id: webhook.id});

Via cURL

# Create webhook
curl -X POST "https://site.com/ghost/api/admin/webhooks/" \
  -H "Authorization: Ghost {jwt-token}" \
  -H "Content-Type: application/json" \
  -d '{
    "webhooks": [{
      "event": "post.published",
      "target_url": "https://example.com/hook",
      "name": "My Webhook"
    }]
  }'

# Delete webhook
curl -X DELETE "https://site.com/ghost/api/admin/webhooks/{id}/" \
  -H "Authorization: Ghost {jwt-token}"

Important: There is no Browse endpoint for webhooks — they cannot be listed via the API.

Webhook Payload

Webhooks send an HTTP POST with a JSON payload containing the relevant resource data:

{
  "post": {
    "current": {
      "id": "...",
      "title": "My Post",
      "slug": "my-post",
      "status": "published",
      "published_at": "2024-01-15T12:00:00.000Z",
      "...": "full post object"
    },
    "previous": {
      "title": "Old Title",
      "updated_at": "2024-01-14T10:00:00.000Z"
    }
  }
}
  • current contains the full current state of the resource
  • previous contains only the fields that changed (for edit events)
  • A 2xx response is considered successful delivery
  • Response body contents are discarded by Ghost

Important caveats:

  • Ghost does not formally document exact payload schemas per event — test empirically or inspect Ghost source
  • No documented retry logic — no retry count, backoff strategy, or timeout is specified
  • The secret field exists on webhook creation but the signature verification mechanism is undocumented — no HMAC algorithm, header name, or verification procedure is publicly specified
  • There is no Browse endpoint — webhooks cannot be listed via the API

Webhook Receiver Example

// Express.js webhook receiver
import express from 'express';
const app = express();
app.use(express.json());

app.post('/webhooks/ghost', (req, res) => {
  const { post, page, tag, member } = req.body;

  if (post) {
    console.log(`Post event: ${post.current.title}`);
    // Trigger rebuild, send notification, etc.
  }

  if (member) {
    console.log(`Member event: ${member.current.email}`);
    // Sync to CRM, send welcome email, etc.
  }

  res.status(200).send('OK');
});

app.listen(3000);

JavaScript SDKs

Content API SDK (@tryghost/content-api)

Read-only access to published content:

npm install @tryghost/content-api
import GhostContentAPI from '@tryghost/content-api';

const api = new GhostContentAPI({
  url: 'https://demo.ghost.io',
  key: 'content-api-key',
  version: 'v5.0'
});

// Browse posts
const posts = await api.posts.browse({
  limit: 10,
  include: 'tags,authors',
  filter: 'featured:true'
});

// Read single post
const post = await api.posts.read({slug: 'welcome'});

// All resources: posts, pages, authors, tags, tiers, settings

Admin API SDK (@tryghost/admin-api)

Full read-write access (server-side only):

npm install @tryghost/admin-api
import GhostAdminAPI from '@tryghost/admin-api';

const api = new GhostAdminAPI({
  url: 'https://my-ghost-site.com',
  key: '{id}:{secret}',
  version: 'v5.0'
});

// CRUD operations
const post = await api.posts.add({title: 'New Post'});
await api.posts.edit({id: post.id, title: 'Updated', updated_at: post.updated_at});
await api.posts.delete({id: post.id});

// Image upload
const image = await api.images.upload({file: '/path/to/image.jpg'});

Helper Libraries

npm install @tryghost/helpers    # Formatting utilities
npm install @tryghost/string     # Slug generation

Common Integration Patterns

Static Site Rebuild

Trigger a Netlify/Vercel rebuild when content changes:

// Webhook receiver
app.post('/webhooks/ghost', async (req, res) => {
  // Trigger Vercel deploy hook
  await fetch('https://api.vercel.com/v1/integrations/deploy/prj_xxx/hook_xxx', {
    method: 'POST'
  });
  res.status(200).send('OK');
});

Or use site.changed webhook directly pointed at a Vercel/Netlify deploy hook URL.

Slack Notification on Publish

app.post('/webhooks/ghost-slack', async (req, res) => {
  const post = req.body.post?.current;
  if (!post) return res.status(200).send('OK');

  await fetch(process.env.SLACK_WEBHOOK_URL, {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
      text: `New post published: *${post.title}*\n${post.url}`
    })
  });

  res.status(200).send('OK');
});

Member Sync to CRM

app.post('/webhooks/ghost-crm', async (req, res) => {
  const member = req.body.member?.current;
  if (!member) return res.status(200).send('OK');

  // Sync to Mailchimp, HubSpot, etc.
  await syncToCRM({
    email: member.email,
    name: member.name,
    status: member.status,
    labels: member.labels
  });

  res.status(200).send('OK');
});

Headless CMS Setups

Ghost works as a headless CMS with popular frameworks:

Next.js

// lib/ghost.js
import GhostContentAPI from '@tryghost/content-api';

export const ghost = new GhostContentAPI({
  url: process.env.GHOST_URL,
  key: process.env.GHOST_CONTENT_API_KEY,
  version: 'v5.0'
});

// app/page.tsx (Next.js App Router)
export default async function Home() {
  const posts = await ghost.posts.browse({
    limit: 10,
    include: 'tags,authors'
  });
  return posts.map(post => <PostCard key={post.id} post={post} />);
}

Gatsby

// gatsby-config.js
module.exports = {
  plugins: [{
    resolve: 'gatsby-source-ghost',
    options: {
      apiUrl: 'https://my-ghost-site.com',
      contentApiKey: 'content-api-key'
    }
  }]
};

Eleventy (11ty)

// _data/posts.js
const GhostContentAPI = require('@tryghost/content-api');
const api = new GhostContentAPI({
  url: 'https://my-ghost-site.com',
  key: 'content-api-key',
  version: 'v5.0'
});

module.exports = async function() {
  return await api.posts.browse({limit: 'all', include: 'tags,authors'});
};

Other Supported Frameworks

Ghost has documented integrations with: Nuxt, Gridsome, VuePress, Hexo, and Hugo.

Migration

Ghost provides migration tools for importing content from other platforms:

Supported Source Platforms

BeeHiiv, Buttondown, Ghost-to-Ghost, Gumroad, Jekyll, Kit (ConvertKit), Mailchimp, Medium, Memberful, Newspack, Patreon, Squarespace, Substack, WordPress

Developer Migration

For custom migrations, use the Admin API to import content:

const api = new GhostAdminAPI({
  url: 'https://new-ghost-site.com',
  key: '{id}:{secret}',
  version: 'v5.0'
});

// Import posts from external source
for (const item of externalPosts) {
  await api.posts.add({
    title: item.title,
    html: item.content,
    status: 'published',
    published_at: item.date,
    tags: item.tags.map(t => ({name: t})),
    feature_image: item.image
  }, {source: 'html'});
}

// Import members
for (const subscriber of externalSubscribers) {
  await api.members.add({
    email: subscriber.email,
    name: subscriber.name,
    labels: [{name: 'imported'}]
  });
}

Ghost Custom Integrations

Create integrations in Ghost Admin > Settings > Integrations:

Each integration provides:

  • Content API Key — For read-only access
  • Admin API Key — For read-write access (id:secret format)
  • Webhook Configuration — Event subscriptions

Integrations have fixed, limited permissions. For full API access, use Staff Access Tokens (from user profile) or User Authentication (session-based).

API Versioning

  • Ghost maintains backward compatibility within major versions
  • Use Accept-Version: v5.0 header to target minimum version
  • Breaking changes only occur in major version bumps (v5 → v6)
  • The ?limit=all parameter was removed in Ghost 6.0 (max 100 per page)
Related skills
Installs
2
First Seen
Apr 11, 2026
Security Audits