Skip to content

Ghost Webhooks — Post Published, Member Added and Custom Targets

DodaTech Updated 2026-06-28 9 min read

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

  1. Go to Settings > Integrations.
  2. Click "Add custom integration" or edit an existing one.
  3. Scroll to the "Webhooks" section.
  4. Click "Add webhook."
  5. 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
  6. 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:

  1. webhook.site — Generates a unique URL that captures all webhook requests
  2. RequestBin — Similar to webhook.site
  3. ngrok — Exposes your local server to the internet for testing

Test Flow

  1. Get a test URL from webhook.site.
  2. Create a webhook in Ghost pointing to the test URL.
  3. Trigger the event (publish a post, add a member).
  4. Check webhook.site to see the full request payload.
  5. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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

  1. 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).

  2. 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-Signature header. Your endpoint can verify the signature to ensure the request came from Ghost.

  3. 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.

  4. 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

Can I have multiple webhooks for the same event?

Yes. You can add multiple webhooks for the same event, each pointing to a different URL. Ghost sends a separate POST request to each webhook URL when the event fires.

Does the webhook include the full post HTML?

Yes. The post.published payload includes the full HTML content, plaintext, feature image, and metadata. For large posts, the payload can be several KB in size.

Is there a limit on how many webhooks I can create?

Ghost does not enforce a hard limit on webhook count, but each webhook adds processing time to the event. Having hundreds of webhooks for the same event may slow down the publishing process.

Can I send webhooks to internal (local) URLs?

Ghost can send webhooks to any URL that is accessible from the server. Local URLs (localhost, 127.0.0.1) work if the service is on the same machine. For external services, use publicly accessible URLs.

How do I debug webhook delivery failures?

Check the integration settings in Ghost admin for the delivery status. Test with webhook.site. Check your server logs for incoming requests. Verify your endpoint returns a 2xx response within a reasonable timeout.

Mini Project

Your task: Build a multi-service webhook integration.

  1. 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
  2. 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
  3. Test each webhook by triggering the corresponding event.
  4. 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:

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro