> ## Documentation Index
> Fetch the complete documentation index at: https://memberful.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook event reference

> Webhooks notify your website/app of events that happen in Memberful, such as when a member creates an account, a subscription is updated, or a plan is deleted.

export const RelatedDocs = ({link1, link2, link3, link4, link5, link6, link7, link8, link9, link10, className = ""}) => {
  const links = [link1, link2, link3, link4, link5, link6, link7, link8, link9, link10].filter(Boolean);
  if (!links.length) return null;
  return <section className={`related-docs border dark:border-gray-700 rounded-2xl px-6 py-4 mt-6 ${className}`}>
      <p className="mb-2 font-medium">
        <strong>Related help docs:</strong>
      </p>
      <ul className="space-y-1 mb-1 mt-1">
        {links.map((link, index) => <li key={index}>
            <a href={link.url}>{link.label}</a>
          </li>)}
      </ul>
    </section>;
};

export const WebhookPayload = ({event}) => {
  const baseTime = Math.floor(Date.now() / 1000);
  const DAY = 60 * 60 * 24;
  const MONTH = DAY * 30;
  const stringTimestamp = time => new Date(time * 1000).toISOString();
  const PRODUCT = {
    id: 0,
    name: "Sample download",
    price: 1000,
    slug: "0-sample-download",
    for_sale: true,
    type: "download"
  };
  const PASS = {
    id: 0,
    name: "Sample plan"
  };
  const PLAN = {
    id: 0,
    price: 1000,
    name: "Sample plan",
    label: "$10/month",
    slug: "0-sample-plan",
    renewal_period: "monthly",
    interval_unit: "month",
    interval_count: 1,
    for_sale: true,
    pass: PASS,
    trial_type: "none",
    trial_price: null,
    type: "standard_plan"
  };
  const SUBSCRIPTION_PLAN = {
    id: 0,
    interval_count: 1,
    interval_unit: "month",
    label: "$10/month",
    name: "Sample plan",
    price_cents: 100000000,
    slug: "0-sample-plan",
    trial_type: "paid",
    trial_price_cents: 1000
  };
  const MEMBER = {
    address: {
      street: "Street",
      city: "City",
      state: "State",
      postal_code: "Postal code",
      country: "City",
      line2: ""
    },
    created_at: baseTime,
    credit_card: {
      exp_month: 1,
      exp_year: 2040
    },
    custom_field: "Custom field value",
    discord_user_id: "000000000000000000",
    email: "john.doe@example.com",
    first_name: "John",
    full_name: "John Doe",
    id: 6945121,
    last_name: "Doe",
    phone_number: "555-12345",
    signup_method: "checkout",
    stripe_customer_id: "cus_00000",
    tracking_params: {
      utm_term: "shoes",
      utm_campaign: "summer_sale",
      utm_medium: "social",
      utm_source: "instagram",
      utm_content: "textlink"
    },
    unrestricted_access: false,
    username: "john_doe"
  };
  const SUBSCRIPTION = {
    active: true,
    autorenew: true,
    activated_at: stringTimestamp(baseTime),
    created_at: stringTimestamp(baseTime),
    expires_at: stringTimestamp(baseTime + MONTH),
    id: 1,
    member_id: 6945121,
    member_price_cents: 12500,
    member: MEMBER,
    pass: PASS,
    subscription_plan: SUBSCRIPTION_PLAN,
    trial_end_at: null,
    trial_start_at: null
  };
  const ORDER = {
    uuid: "4DACB7B0-B728-0130-F9E8-102B343DC979",
    created_at: 1765315078,
    number: "4DACB7B0",
    total: 9900,
    status: "completed",
    receipt: "receipt text",
    member: MEMBER,
    products: [],
    subscriptions: [{
      activated_at: baseTime,
      active: true,
      created_at: baseTime,
      expires: true,
      expires_at: baseTime + MONTH,
      id: 0,
      in_trial_period: false,
      pass: PASS,
      renew_at_end_of_period: true,
      subscription: {
        id: 0,
        name: "Sample plan",
        slug: "0-sample-plan",
        interval_unit: "month",
        interval_count: 1,
        type: "standard_plan"
      },
      trial_end_at: null,
      trial_start_at: null
    }]
  };
  const CUSTOM_FIELDS = [{
    field: {
      id: 1,
      label: "Who's your favorite writer?"
    },
    value: "John Doe"
  }, {
    field: {
      id: 2,
      label: "What's your t-shirt size?"
    },
    value: ""
  }, {
    field: {
      id: 3,
      label: "How do you consume our content?"
    },
    value: ["Podcast", "Newsletter"]
  }];
  const TAX_ID = {
    country: "FR",
    type: "eu_vat",
    value: "FR1234567890"
  };
  const WEBHOOKS = {
    member_signup: {
      event: "member_signup",
      member: MEMBER
    },
    member_updated: {
      event: "member_updated",
      member: MEMBER,
      changed: {
        email: ["old_email@example.com", MEMBER.email]
      }
    },
    "member.deleted": {
      event: "member.deleted",
      member: {
        deleted: true,
        id: 0
      }
    },
    "order.completed": {
      event: "order.completed",
      order: ORDER
    },
    "order.purchased": {
      event: "order.purchased",
      order: ORDER
    },
    "order.refunded": {
      event: "order.refunded",
      order: {
        ...ORDER,
        status: "refunded"
      }
    },
    "order.suspended": {
      event: "order.suspended",
      order: {
        ...ORDER,
        status: "suspended"
      }
    },
    "subscription_plan.created": {
      event: "subscription_plan.created",
      subscription: PLAN
    },
    "subscription_plan.updated": {
      event: "subscription_plan.updated",
      subscription: PLAN
    },
    "subscription_plan.deleted": {
      event: "subscription_plan.deleted",
      subscription: PLAN
    },
    "download.created": {
      event: "download.created",
      product: PRODUCT
    },
    "download.deleted": {
      event: "download.deleted",
      product: PRODUCT
    },
    "download.updated": {
      event: "download.updated",
      product: PRODUCT
    },
    "subscription.created": {
      event: "subscription.created",
      subscription: SUBSCRIPTION
    },
    "subscription.activated": {
      event: "subscription.activated",
      subscription: SUBSCRIPTION
    },
    "subscription.deactivated": {
      event: "subscription.deactivated",
      subscription: {
        ...SUBSCRIPTION,
        active: false
      }
    },
    "subscription.deleted": {
      event: "subscription.deleted",
      subscription: SUBSCRIPTION
    },
    "subscription.renewed": {
      event: "subscription.renewed",
      subscription: SUBSCRIPTION,
      order: {
        created_at: stringTimestamp(baseTime),
        status: "completed",
        total: 9900,
        uuid: "4DACB7B0-B728-0130-F9E8-102B343DC979"
      }
    },
    "subscription.updated": {
      event: "subscription.updated",
      subscription: SUBSCRIPTION,
      changed: {
        plan_id: [42, SUBSCRIPTION.subscription_plan.id],
        expires_at: [stringTimestamp(baseTime + MONTH), stringTimestamp(baseTime + 2 * MONTH)],
        autorenew: [false, SUBSCRIPTION.autorenew]
      }
    },
    "custom_fields.updated": {
      event: "custom_fields.updated",
      member: {
        ...MEMBER,
        custom_field: undefined
      },
      custom_fields: CUSTOM_FIELDS
    },
    "tax_id.updated": {
      event: "tax_id.updated",
      member: MEMBER,
      tax_id: TAX_ID
    }
  };
  const payload = WEBHOOKS[event];
  if (!payload) {
    return <div className="bg-red-50 border border-red-200 rounded-md p-4">
        <p className="text-red-800 text-sm">
          Unknown webhook event: <code>{event}</code>
        </p>
      </div>;
  }
  const formattedJson = JSON.stringify(payload, null, 2);
  return <CodeBlock>{formattedJson}</CodeBlock>;
};

This article lists all the events that can trigger a webhook. If you're looking for an explanation of how to set up and use webhooks, check out [our main article about Webhooks](/api-reference/webhooks/).

## Plans and prices in webhook payloads

The Memberful dashboard uses the terms **Plan** and **Price**, but webhook payloads use different names for the same concepts:

| Dashboard | Webhook field                        | Description                                                        |
| --------- | ------------------------------------ | ------------------------------------------------------------------ |
| Plan      | `pass`                               | What a member subscribes to—controls access and entitlements       |
| Price     | `subscription_plan` / `subscription` | A pricing option within a plan (e.g. "\$10/month" or "\$100/year") |

A pass can have multiple plans—for example, a "Premium" pass might offer both a \$10/month and a \$100/year plan. All plans on the same pass give members the same access, so **if your integration makes access decisions, use the `pass` object, not the plan-level fields**.

Subscription, order, and plan (price) webhook payloads include a `pass` object with `id` and `name`. The `label` field on a plan contains an internal price description like "\$10 monthly".

<Info>
  Webhook event names like `subscription_plan.created` refer to prices, not plans—the naming predates the current terminology.
</Info>

## Member events

Member accounts are created automatically when a member purchases a subscription or [registers for free](/member-management/payments-and-access/give-free-access/#enable-free-registration) (if that feature is enabled). You can also [create member accounts manually](/manage-your-members/add-or-delete-a-member/) from your dashboard.

<Info>
  Most webhook types follow the *object.event* naming convention. Member events currently follow an *object\_event* convention instead. Pay close attention to the event names laid out in this doc.
</Info>

#### member\_signup

Sent when a new member account is created.

Use this webhook to add new members to your app or to a third-party service.

<WebhookPayload event="member_signup" />

#### member\_updated

Sent when a member's profile information is updated.

Use this webhook to update a member's profile information in your app.

This will *not* be triggered when a member updates their answers to custom fields. Use the [custom\_fields.updated](#custom_fields-updated) webhook to detect custom field updates.

<WebhookPayload event="member_updated" />

#### member.deleted

Sent when a member is deleted from your Memberful account.

Use this webhook to remove a member from your app if they were deleted from Memberful.

It's not common for a member account to be deleted — in most cases, you'll want to react to [Subscription Deactivated](#subscription-deactivated) instead.

<WebhookPayload event="member.deleted" />

#### tax\_id.updated

Sent when a member adds, changes, or removes their tax ID.

Use this trigger to update your app when a member updates their tax ID.

<WebhookPayload event="tax_id.updated" />

#### custom\_fields.updated

Sent when a member answers or updates their custom fields. The `value` field can be a String, an Array of Strings, or an empty Array.

Use this trigger to update your app when a member updates their answers to custom fields, either during checkout or by visiting their member profile.

<WebhookPayload event="custom_fields.updated" />

## Subscription events

Sent when a member subscribes to a plan or when that subscription is updated, renewed, or deleted.

<Info>
  Almost all times across all webhooks are returned as Unix time, but subscription events use ISO 8601 for the following attributes: `activated_at`, `created_at`, `expires_at`, `trial_end_at`, and `trial_start_at`.
</Info>

#### subscription.created

Sent when a new subscription is added to a member's account. This includes when a member purchases a subscription or activates a gifted subscription, when a member is added to a group subscription, and when a staff account manually creates a subscription.

Use this webhook to let your app know when a member has subscribed.

<WebhookPayload event="subscription.created" />

Signups from group managers and members both trigger the **subscription.created** event. To differentiate between the two, you can check the two member IDs in the data that was returned.

If both IDs are the same, the signup is from a group manager (or it's for a plan that doesn't support groups). If the IDs are different, the signup is from a member who is joining a group. The group manager's ID is the one at the top, while the group member's ID appears further down (nested within the <em>member</em> object).

<img src="https://mintcdn.com/memberful/DOpS1_0MI9MjYqsN/images/custom-development-and-api/webhook-event-reference/group-manager-vs-member-id.png?fit=max&auto=format&n=DOpS1_0MI9MjYqsN&q=85&s=b4803db2a22f631a1cab028d6ab9d018" class="border" alt="Group Manager vs Member ID" width="1440" height="754" data-path="images/custom-development-and-api/webhook-event-reference/group-manager-vs-member-id.png" />

#### subscription.updated

Sent when a member's subscription is updated.

If you want to know if the update is a **plan change,** you'll need to see if the *plan\_id* field is present in the *changed* object. The first value is the old plan and the second value is the new one. The same applies to other changed fields, like *autorenew*.

<WebhookPayload event="subscription.updated" />

#### subscription.renewed

Sent when a member's subscription is renewed or when a returning member reactivates an old subscription.

Use this webhook to renew the member's access to your app. At this time there's no way to differentiate between a renewal and a reactivation, but you could reach out to [our API](/api-reference/memberful-api/) to find out more about the subscription's history.

<WebhookPayload event="subscription.renewed" />

#### subscription.activated

Sent when a suspended order is marked completed by staff and the subscription becomes active again.

Sent when a member's [suspended order](/member-management/subscription-management/pause-a-subscription/#suspend-the-order) is marked by staff as *complete* and the subscription becomes active again.

This does *not* refer to when a member reactivates a previously expired subscription — use the [Subscription Renewed](#subscription-renewed) trigger for that.

<WebhookPayload event="subscription.activated" />

#### subscription.deactivated

Sent when a member's subscription fails to renew, expires, or becomes inactive. Also sent when a staff account [suspends an order](/member-management/subscription-management/pause-a-subscription/#suspend-the-order), making the subscription inactive.

Use this webhook to remove the member's access to your app or to update their status if they stop paying.

<WebhookPayload event="subscription.deactivated" />

#### subscription.deleted

Sent when a staff account deletes a member's subscription from the Memberful dashboard.

Use this webhook to remove the member's access to your app or to update their status.

<WebhookPayload event="subscription.deleted" />

## Order events

Sent when a member places an order to purchase plans or downloads.

<Callout icon="triangle-alert" color="#FFE044">
  Custom fields are now collected after checkout, which means they're no longer set when the `order_purchased` event is triggered. To access them, webhook recipients must now use the `custom_fields.updated` event instead.
</Callout>

#### order.purchased

Sent when a member places an order or when a staff account manually adds an order to a member's account.

This is not triggered for renewal payments.

A member purchasing a gift subscription for someone else will trigger this webhook, but no subscription will be created until the recipient activates their gift.

Use this webhook to notify your app when a member makes a purchase.

<WebhookPayload event="order.purchased" />

#### order.refunded

Sent when a staff account refunds an order.

Use this trigger to update your app when a refund has been processed.

<WebhookPayload event="order.refunded" />

#### order.suspended

Sent when an order is suspended by staff.

<WebhookPayload event="order.suspended" />

#### order.completed

Sent when a suspended order is marked completed by staff.

<WebhookPayload event="order.completed" />

## Plan events

Sent when staff accounts create, update, or delete prices. These events use `subscription_plan` in their names and payloads—see [Plans and prices in webhook payloads](#plans-and-prices-in-webhook-payloads) for how that maps to dashboard terminology.

#### subscription\_plan.created

Sent when a new price is created.

<WebhookPayload event="subscription_plan.created" />

#### subscription\_plan.updated

Sent when a price is updated.

<WebhookPayload event="subscription_plan.updated" />

#### subscription\_plan.deleted

Sent when a price is deleted.

<WebhookPayload event="subscription_plan.deleted" />

## Download events

Staff can create downloads to include with plans or to sell separately.

#### download.created

Sent when a download is created.

<WebhookPayload event="download.created" />

#### download.updated

Sent when a download is updated.

<WebhookPayload event="download.updated" />

#### download.deleted

Sent when a download is deleted.

<WebhookPayload event="download.deleted" />

## How upgrades/downgrades are handled

Both upgrades and downgrades trigger the [subscription.updated webhook.](#subscription-updated)

Upgrades include a "changed" section detailing the changes:

<CodeGroup>
  ```json subscription.updated theme={null}
  "changed": {
      "plan_id": [
        42,
        0
      ],
      "expires_at": [
        "2023-05-12T15:09:58Z",
        "2023-06-11T15:09:58Z"
      ],
      "autorenew": [
        false,
        true
      ]
    }
  ```
</CodeGroup>

Downgrades tend to happen on the next renewal date (since the member already paid a higher price for their current period, they're not downgraded immediately). In that case, the "Changed" section will be empty.

<RelatedDocs
  link1={{
url: "/api-reference/webhooks/",
label: "Learn to use Memberful's webhooks",
}}
  link2={{
url: "/api-reference/memberful-api",
label: "Learn to use Memberful's API",
}}
/>
