Skip to main content

Overview

Selling plan webhooks notify your app when selling plan groups are created or updated. Selling plan groups define the subscription offerings available to customers, including billing frequencies, pricing policies, and delivery schedules.
In Shopify’s subscription model, selling plan groups contain multiple selling plans that define different subscription options (e.g., monthly vs. yearly subscriptions).

Webhook Topics

selling_plan_groups/create

Triggered when a new selling plan group is created.

Webhook Configuration

[[webhooks.subscriptions]]
topics = [ "selling_plan_groups/create", "selling_plan_groups/update" ]
uri = "/webhooks/selling_plan_groups/create_or_update"
The reference app handles both create and update events with the same webhook endpoint since the processing logic is identical.

Payload Structure

id
number
required
The numeric ID of the selling plan group
admin_graphql_api_id
string
required
The GraphQL Admin API ID of the selling plan group (format: gid://shopify/SellingPlanGroup/{id})
admin_graphql_api_app
string
The GraphQL Admin API ID of the app that owns this selling plan group (null if created by merchant)
name
string
required
The display name of the selling plan group (e.g., “Subscribe and Save”)
merchant_code
string
required
A merchant-facing code or identifier for the selling plan group
options
array
required
An array of option names that customers can choose from (e.g., [“Delivery frequency”])
selling_plans
array
required
An array of selling plan objects within this group
name
string
The display name of the selling plan
description
string
A description of the selling plan shown to customers
billing_policy
object
The billing policy for this plan
interval
string
The billing interval unit (e.g., “day”, “week”, “month”, “year”)
interval_count
number
The number of intervals between billings
min_cycles
number
Minimum number of billing cycles (null if no minimum)
max_cycles
number
Maximum number of billing cycles (null if unlimited)
delivery_policy
object
The delivery policy for this plan
interval
string
The delivery interval unit
interval_count
number
The number of intervals between deliveries
pricing_policies
array
An array of pricing policy objects that define discounts
adjustment_type
string
The type of pricing adjustment (e.g., “percentage”, “fixed_amount”)
adjustment_value
string
The value of the adjustment (e.g., “10” for 10% off)

Example Payload

{
  "id": 123456789,
  "admin_graphql_api_id": "gid://shopify/SellingPlanGroup/123456789",
  "admin_graphql_api_app": "gid://shopify/App/987654321",
  "name": "Subscribe and Save",
  "merchant_code": "subscribe-save-2024",
  "options": ["Delivery frequency"],
  "selling_plans": [
    {
      "name": "Monthly Subscription",
      "description": "Delivered every month, save 10%",
      "billing_policy": {
        "interval": "month",
        "interval_count": 1,
        "min_cycles": null,
        "max_cycles": null
      },
      "delivery_policy": {
        "interval": "month",
        "interval_count": 1
      },
      "pricing_policies": [
        {
          "adjustment_type": "percentage",
          "adjustment_value": "10"
        }
      ]
    },
    {
      "name": "Annual Subscription",
      "description": "Delivered every year, save 20%",
      "billing_policy": {
        "interval": "year",
        "interval_count": 1,
        "min_cycles": 1,
        "max_cycles": null
      },
      "delivery_policy": {
        "interval": "year",
        "interval_count": 1
      },
      "pricing_policies": [
        {
          "adjustment_type": "percentage",
          "adjustment_value": "20"
        }
      ]
    }
  ]
}

Handler Implementation

The reference app creates translations for selling plans to support multi-language stores:
// app/routes/webhooks.selling_plan_groups.create_or_update.tsx
import type {ActionFunctionArgs} from '@remix-run/node';
import {CreateSellingPlanTranslationsJob, jobs} from '~/jobs';
import {authenticate} from '~/shopify.server';
import {logger} from '~/utils/logger.server';

export const action = async ({request}: ActionFunctionArgs) => {
  const {topic, shop, payload} = await authenticate.webhook(request);

  logger.info({topic, shop, payload}, 'Received webhook');

  // Enqueue job to create translations for all selling plans
  jobs.enqueue(
    new CreateSellingPlanTranslationsJob({
      shop,
      payload: payload,
    }),
  );

  return new Response();
};
The translation job processes each selling plan in the group and creates translations for the plan name and description in all locales configured for the store.

selling_plan_groups/update

Triggered when an existing selling plan group is updated.

Webhook Configuration

Same as selling_plan_groups/create - both topics share the same webhook endpoint.

Payload Structure

The payload structure is identical to the create webhook, containing the updated selling plan group data.

Handler Implementation

The handler implementation is the same as for create events, as shown above. When a selling plan group is updated, the translation job re-processes all selling plans to ensure translations are current.

Understanding Selling Plans

Selling Plan Groups

A selling plan group is a collection of related subscription options. For example, a “Subscribe and Save” group might contain:
  • Monthly subscription (10% off)
  • Quarterly subscription (15% off)
  • Annual subscription (20% off)

Billing vs. Delivery Policies

  • Billing Policy: Defines when the customer is charged (e.g., every month)
  • Delivery Policy: Defines when the product is delivered (e.g., every month)
These can differ. For example, you might bill annually but deliver monthly.

Pricing Policies

Pricing policies define the discount or adjustment applied to subscription purchases:
Adjustment TypeDescriptionExample
percentagePercentage discount”10” = 10% off
fixed_amountFixed amount discount”5.00” = $5 off
priceFixed subscription price”29.99” = $29.99 per cycle

Translation Job Implementation

The reference app uses a background job to create translations:
// app/jobs/webhooks/CreateSellingPlanTranslationsJob.ts
import {BaseJob} from '../BaseJob';
import type {Jobs, Webhooks} from '~/types';

export class CreateSellingPlanTranslationsJob extends BaseJob {
  async perform(
    params: Jobs.Parameters<Webhooks.SellingPlanGroups>
  ): Promise<void> {
    const {shop, payload} = params;
    const {admin_graphql_api_id, selling_plans} = payload;

    // Get all available locales for the shop
    const locales = await this.getShopLocales(shop);

    // Create translations for each selling plan
    for (const plan of selling_plans) {
      await this.createTranslations(
        shop,
        admin_graphql_api_id,
        plan,
        locales
      );
    }
  }

  private async createTranslations(
    shop: string,
    groupId: string,
    plan: Webhooks.SellingPlanDetails,
    locales: string[]
  ): Promise<void> {
    // Implementation creates translation records
    // for plan name and description in all locales
  }

  private async getShopLocales(shop: string): Promise<string[]> {
    // Query shop settings to get enabled locales
    // Returns array like ['en', 'fr', 'es']
  }
}

Best Practices

  1. Translation Support: Always create translations for selling plans if you support multiple languages
  2. Validation: Validate selling plan configurations to ensure billing and delivery policies make sense
  3. Change Detection: When processing update webhooks, compare with stored data to detect what changed
  4. Product Associations: Track which products are associated with which selling plan groups
  5. Testing: Test with different billing intervals and pricing policies to ensure correct behavior
  6. Caching: Consider caching selling plan data to avoid repeated API calls

Common Use Cases

Creating Localized Subscription Options

Use the create webhook to automatically generate translations:
// When a selling plan group is created with name "Subscribe and Save"
// Create translations:
// - EN: "Subscribe and Save"
// - FR: "Abonnez-vous et économisez"
// - ES: "Suscríbete y ahorra"

Syncing with External Systems

Use both create and update webhooks to keep subscription offerings in sync:
// Sync selling plan data to external billing system
// Update analytics dashboard with new subscription options
// Trigger marketing campaigns for new subscription products

Monitoring Selling Plan Changes

Track changes to selling plans for audit and analytics purposes:
// Log all changes to selling plan configurations
// Alert team when pricing policies change
// Track which plans are most popular based on subscription creation data

Build docs developers (and LLMs) love