Ghost Webhooks — Post Published, Member Added and Custom Targets
In this tutorial, you'll learn how to use Ghost webhooks — automated HTTP callbacks that notify external services when events happen on your Ghost site, such as publishing a post or a new member signing up.
What You'll Learn
- What webhooks are and how they work in Ghost
- Available webhook events: posts, pages, tags, members
- Setting up webhooks in the Ghost admin
- Webhook payload format and structure
- Testing webhooks with webhook testing tools
- Creating webhooks via the Admin API
- Securing webhooks with verification tokens
- Common webhook integration patterns
- Troubleshooting webhook delivery issues
Why It Matters
Webhooks connect Ghost to the rest of your tech stack. When you publish a post, a webhook can notify your team on Slack, regenerate your static site, update your CMS index, or log the event to your analytics platform. Instead of checking Ghost manually or polling the API, webhooks deliver real-time notifications directly to your systems.
Real-World Use
A content team publishes articles throughout the day. Each time a post is published, a webhook fires: it posts the article title and URL to the company Slack channel, triggers a Netlify re-deploy for their static marketing site, and adds the article to their internal content tracker database. The webhooks run automatically — no one remembers to notify the team manually.
Learning Path
flowchart LR A["Admin API"] --> B["Webhooks
You are here"]:::current B --> C["Custom Integrations"] C --> D["Headless Ghost"] classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
What is a Webhook?
A webhook is an HTTP callback — a POST request that Ghost sends to a URL you specify when a specific event occurs.
flowchart LR A["Event fires in Ghost"] --> B["Ghost sends POST request"] B --> C["Your webhook URL"] C --> D["Your service processes the event"] style B fill:#38bdf8,color:#0f172a
Unlike polling (where you repeatedly check the API for changes), webhooks deliver events immediately as they happen. This is more efficient and provides real-time notifications.
Available Webhook Events
Ghost supports webhooks for these event types:
Content Events
| Event | When It Fires |
|---|---|
| post.published | A post is published |
| post.scheduled | A post is scheduled |
| post.unpublished | A post is unpublished |
| post.deleted | A post is deleted |
| post.edited | A post is edited |
| page.published | A page is published |
| page.deleted | A page is deleted |
| tag.added | A new tag is created |
| tag.deleted | A tag is deleted |
Member Events
| Event | When It Fires |
|---|---|
| member.added | A new member signs up |
| member.deleted | A member is deleted |
| member.edited | A member's profile is updated |
| subscription.created | A new subscription starts |
| subscription.updated | A subscription changes |
| subscription.deleted | A subscription is canceled/destroyed |
Setting Up Webhooks
Via Ghost Admin
- Go to
Settings > Integrations. - Click "Add custom integration" or edit an existing one.
- Scroll to the "Webhooks" section.
- Click "Add webhook."
- Configure:
- Name: A descriptive name (e.g., "Slack Notifications")
- Event: Select the event that triggers this webhook
- Target URL: The URL that receives the POST request
- Secret: Optional secret for request verification
- Click "Save."
Via Admin API
const GhostAdminAPI = require('@tryghost/admin-api');
const api = new GhostAdminAPI({
url: 'https://yoursite.com',
key: 'YOUR_ADMIN_API_KEY',
version: 'v5.0'
});
const webhook = await api.webhooks.add({
name: 'Post Published Slack Notification',
event: 'post.published',
target_url: 'https://hooks.slack.com/services/TXXX/BXXX/XXXX',
secret: 'my-webhook-secret'
});
Webhook Payload Format
When a webhook fires, Ghost sends a POST request with a JSON body.
Post Published Payload
{
"post": {
"current": {
"id": "64a1b2c3d4e5f6",
"uuid": "abc-def-ghi",
"title": "My New Post",
"slug": "my-new-post",
"html": "<p>Post content...</p>",
"plaintext": "Post content...",
"feature_image": "https://yoursite.com/content/images/image.jpg",
"featured": false,
"visibility": "public",
"status": "published",
"created_at": "2024-01-15T10:00:00.000Z",
"published_at": "2024-01-15T10:00:00.000Z",
"updated_at": "2024-01-15T10:00:00.000Z",
"url": "https://yoursite.com/my-new-post/"
}
}
}
Member Added Payload
{
"member": {
"current": {
"id": "64a1b2c3d4e5f6",
"uuid": "abc-def-ghi",
"name": "John Doe",
"email": "john@example.com",
"status": "free",
"created_at": "2024-01-15T10:00:00.000Z",
"labels": ["website-signup"],
"tiers": [{
"id": "TIER_ID",
"name": "Free"
}]
}
}
}
Subscription Created Payload
{
"member": {
"current": {
"id": "MEMBER_ID",
"email": "john@example.com",
"status": "paid",
"tiers": [{
"name": "Monthly Premium"
}]
}
},
"subscription": {
"current": {
"id": "SUB_ID",
"status": "active",
"plan": {
"amount": 900,
"currency": "usd",
"interval": "month"
}
}
}
}
Testing Webhooks
Using Webhook Testing Tools
Before connecting to a real service, test webhooks with tools like:
- webhook.site — Generates a unique URL that captures all webhook requests
- RequestBin — Similar to webhook.site
- ngrok — Exposes your local server to the internet for testing
Test Flow
- Get a test URL from webhook.site.
- Create a webhook in Ghost pointing to the test URL.
- Trigger the event (publish a post, add a member).
- Check webhook.site to see the full request payload.
- Verify the payload structure matches your expectations.
Verifying Webhook Signatures
If you set a secret when creating the webhook, Ghost signs the request body with that secret.
Verification (Node.js)
const crypto = require('crypto');
function verifyWebhook(body, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const digest = hmac.update(JSON.stringify(body)).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(digest),
Buffer.from(signature)
);
}
// Express middleware
app.post('/webhook', (req, res) => {
const signature = req.headers['x-ghost-signature'];
const secret = process.env.WEBHOOK_SECRET;
if (!verifyWebhook(req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}
// Process the webhook
res.status(200).send('OK');
});
The signature is sent in the X-Ghost-Signature header.
Common Webhook Patterns
Slack Notification
app.post('/webhook/post-published', async (req, res) => {
const post = req.body.post.current;
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');
});
Static Site Rebuild (Netlify)
app.post('/webhook/post-published', async (req, res) => {
await fetch(`https://api.netlify.com/build_hooks/${process.env.NETLIFY_HOOK_ID}`, {
method: 'POST'
});
res.status(200).send('OK');
});
Google Sheets Logging
app.post('/webhook/member-added', async (req, res) => {
const member = req.body.member.current;
// Log member info to Google Sheets via API
await appendToSheet({
spreadsheetId: process.env.SHEET_ID,
range: 'Members!A:D',
values: [[member.name, member.email, member.created_at, member.status]]
});
res.status(200).send('OK');
});
Retry and Delivery
Delivery Behavior
- Ghost attempts to deliver the webhook immediately when the event fires.
- If the endpoint returns a non-2xx status, Ghost retries.
- Retry interval: Ghost retries up to 3 times with increasing delays.
- After 3 failed attempts, the webhook is marked as failed.
Monitoring Webhooks
In Ghost admin, the integration settings show:
- Last delivery: When the last webhook was sent
- Status: Success or failed
- Response code: The HTTP status from your endpoint
Common Mistakes
Not returning a 2xx status: Your webhook endpoint must return a 200, 201, or 204 status to acknowledge receipt. If you return a 4xx or 5xx, Ghost retries. If the endpoint does not respond at all, the request times out.
Assuming webhooks fire immediately for all events: Some events have slight delays. Post.published fires at publication time. Member.added fires immediately on signup. But some internal processing may introduce small delays.
Not verifying webhook signatures: Without signature verification, anyone who knows your webhook URL can send fake events. Always verify the signature if you set a secret.
Processing the same event multiple times: Ghost may deliver the same webhook more than once. Your handler should be idempotent — processing the same event twice should not cause duplicates.
Using webhooks for real-time requirements without testing latency: Webhooks are near-real-time but not instantaneous. Network latency, retries, and processing time add up. For true real-time needs, consider WebSockets or Server-Sent Events.
Practice Questions
What events can trigger a Ghost webhook? Answer: Content events (post.published, post.scheduled, post.deleted, post.edited, page.published, page.deleted, tag.added, tag.deleted) and member events (member.added, member.deleted, member.edited, subscription.created, subscription.updated, subscription.deleted).
How does Ghost sign webhook requests for security? Answer: When you set a secret for a webhook, Ghost signs the request body using HMAC-SHA256 with that secret. The signature is sent in the
X-Ghost-Signatureheader. Your endpoint can verify the signature to ensure the request came from Ghost.What happens if your webhook endpoint is down? Answer: Ghost retries the delivery up to 3 times with increasing delays. If all retries fail, the webhook is marked as failed in the integration settings. The event is not re-queued after the final failure.
Challenge: Set up a complete webhook integration. Create a webhook for post.published events pointing to a webhook.site test URL. Publish a test post and examine the payload. Then build a simple HTTP server (using Express or similar) that receives post.published webhooks, verifies the signature, posts the title and URL to a Slack channel, and returns a 200 response.
FAQ
Mini Project
Your task: Build a multi-service webhook integration.
- Create a Ghost integration with three webhooks:
- post.published → logs the post to a webhook.site test URL
- member.added → sends a welcome email via a mock email service
- subscription.created → adds the member to a mock CRM
- Build a single webhook receiver endpoint (Express.js) that:
- Routes each event type to the correct handler
- Verifies webhook signatures
- Logs all events to a file
- Returns appropriate status codes
- Test each webhook by triggering the corresponding event.
- Document the full integration architecture.
This exercise gives you a production-ready webhook handling system.
What's Next
Now that you understand webhooks, learn about custom integrations:
Continue to Lesson 28: Custom Integrations — Zapier, Slack, custom apps, and SSO.
Related lessons:
- Headless Ghost — Use Ghost as a headless CMS
- Admin API — Full CRUD operations
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro