Webhooks do not trigger member updates for continuous subscription renewals

Issue Summary

  • Explain roughly what’s wrong

Webhooks in Ghost CMS only trigger member update events when a subscription is initially created or the plan changes. They do not trigger on subsequent (continuous) monthly subscription payments, meaning member status is not refreshed and connected automations (such as in n8n or other integration platforms) cannot respond to ongoing payments.

This makes it impossible for me to rely on Ghost as a platform, since members pass their judgment of the platform not working onto me

  • What did you expect to happen?

Whenever a recurring subscription payment is processed (such as the second month onward for an active subscription), the webhook for member update should be triggered. This would enable external systems to keep member status and automations up-to-date in Ghost CMS.

Steps to Reproduce

  1. Create a paid subscription for a member in Ghost CMS using Stripe as the payment gateway.

  2. Observe that the “member.updated” webhook is triggered at subscription creation.

  3. Wait for the next (second/third…) recurring monthly subscription payment to process via Stripe.

  4. Observe that no new webhook event related to “member.updated” is triggered by Ghost following these recurring payments.

  5. Check external automation (e.g., n8n, Zapier) connected to Ghost webhooks—no trigger on the recurring payment event.

Setup information

Ghost Version

Version 6, but also happened on 5

Node.js Version

The one that comes with Ghost docker image

How did you install Ghost?

Self hosting using Coolify, this is the docker compose

services:
  ghost:
    image: 'ghost:6-alpine'
    volumes:
      - 'ghost-content-data:/var/lib/ghost/content'
      -
        type: bind
        source: ./config.production.json
        target: /var/lib/ghost/config.production.json
    environment:
      - url=$SERVICE_FQDN_GHOST_2368
      - database__client=mysql
      - database__connection__host=mysql
      - database__connection__user=$SERVICE_USER_MYSQL
      - database__connection__password=$SERVICE_PASSWORD_MYSQL
      - 'database__connection__database=${MYSQL_DATABASE-ghost}'
      - mail__transport=SMTP
      - 'mail__from=${MAIL_FROM}'
      - 'mail__options__service=${MAIL_OPTIONS_SERVICE:-SES}'
      - 'mail__options__host=${MAIL_OPTIONS_HOST}'
      - 'mail__options__port=${MAIL_OPTIONS_PORT:-587}'
      - 'mail__options__auth__pass=${MAIL_OPTIONS_AUTH_PASS}'
      - 'mail__options__auth__user=${MAIL_OPTIONS_AUTH_USER}'
      - 'mail__options__secure=${MAIL_OPTIONS_SECURE:-false}'
    depends_on:
      mysql:
        condition: service_healthy
  mysql:
    image: 'mysql:8'
    volumes:
      - 'ghost-mysql-data:/var/lib/mysql'
    environment:
      - 'MYSQL_USER=${SERVICE_USER_MYSQL}'
      - 'MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}'
      - 'MYSQL_DATABASE=${MYSQL_DATABASE}'
      - 'MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}'
    healthcheck:
      test:
        - CMD
        - mysqladmin
        - ping
        - '-h'
        - 127.0.0.1

Provide details of your host & operating system

Hetzner, x86 Ubuntu

Database type

MySQL 8

That would make the member.updated webhook really noisy. For sites using Zapier or other services with limited invocations to detect changes needed for services (like email address changes or subscription tier changes), having subscription renewals in the mix would be bad.

I think the right approach if you need to run automations on renewals is likely to be a Strioe webhook instead.

I think that should be our choice, not silently fail to notify that the user is still subscribed

I don’t think it’s spam if it’s a webhook every month

And yes, while using stripe’s webhooks is a workaround, I don’t believe we should have to rely on a third party for this basic functionality

What happens when there are more payment platforms? Will I have to listen to every platform’s webhooks?

I believe this should be rethought, or at least give us a warning and choice

What if you have a site with 10,000 members?

Stripe is kinda the first party in this case. Ghost is just consuming the data from Stripe as well. In a way, you could argue that Ghost is the third party in this setup.

There aren’t. Ghost is built around Stripe as a subscription processing platform. It is so baked in that you will not run into this problem anytime soon.

In that case, you open a post in Ideas. I also don’t see this as a bug (e.g. intended behavior breaking).

Also, the thing with choice is…any piece of software is opinionated. Ghost has strong opinions about lots of things. Making everything a choice in software quickly becomes a UX and maintenance nightmare.

2 Likes

My use case is for Amplitude analytics, but I have the same problem.

I’ve read everything and even if I agree with Ghost’s position on this (that Stripe webhook should be handled) - there’s nothing in Stripe webhook at the moment that links Stripe + Ghost (ideally like the ghost member uuid). Email can change and cannot be used to identify a reader.

Also I could be wrong here - there’s no guarantee that Ghost member email and stripe account email will be the same, right? But regardless of that point given that email is something that can be changed, cannot be used to identify a user reliably.

Can someone from Ghost answer to this specific point please?