Skip to main content
Glama

create_webhook

Subscribe to Shopify events like orders created, paid, or fulfilled, and have payloads POSTed to your HTTPS endpoint. Optionally filter fields to reduce bandwidth and avoid leaking unrelated data.

Instructions

Subscribe to a Shopify event topic and have payloads POSTed to your HTTPS endpoint. Common topics: ORDERS_CREATE, ORDERS_PAID, ORDERS_FULFILLED, PRODUCTS_CREATE, PRODUCTS_UPDATE, INVENTORY_LEVELS_UPDATE, CUSTOMERS_CREATE, APP_UNINSTALLED. Use includeFields to receive only specific fields in the payload (reduces bandwidth and avoids leaking unrelated data). metafieldNamespaces opts in to including metafields from the listed namespaces. callbackUrl must be HTTPS in production. Returns the new subscription's GID; verify delivery is working with a few real events before relying on it.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
topicYesWebhookSubscriptionTopic, e.g. 'ORDERS_CREATE', 'ORDERS_PAID', 'PRODUCTS_UPDATE', 'INVENTORY_LEVELS_UPDATE', 'APP_UNINSTALLED'. See Shopify docs for full list.
callbackUrlYesHTTPS endpoint that will receive the webhook POSTs.
formatNoJSON
includeFieldsNoOptional: only include these fields in the payload (reduces payload size).
metafieldNamespacesNoOptional: include metafields from these namespaces in the payload.

Implementation Reference

  • The 'create_webhook' tool handler: subscribes to a Shopify event topic, sends a mutation to create a webhook subscription, and returns the result.
    server.tool(
      "create_webhook",
      "Subscribe to a Shopify event topic and have payloads POSTed to your HTTPS endpoint. Common topics: ORDERS_CREATE, ORDERS_PAID, ORDERS_FULFILLED, PRODUCTS_CREATE, PRODUCTS_UPDATE, INVENTORY_LEVELS_UPDATE, CUSTOMERS_CREATE, APP_UNINSTALLED. Use includeFields to receive only specific fields in the payload (reduces bandwidth and avoids leaking unrelated data). metafieldNamespaces opts in to including metafields from the listed namespaces. callbackUrl must be HTTPS in production. Returns the new subscription's GID; verify delivery is working with a few real events before relying on it.",
      createWebhookSchema,
      async (args) => {
        const webhookSubscription: Record<string, unknown> = {
          callbackUrl: args.callbackUrl,
          format: args.format,
        };
        if (args.includeFields) {
          webhookSubscription.includeFields = args.includeFields;
        }
        if (args.metafieldNamespaces) {
          webhookSubscription.metafieldNamespaces = args.metafieldNamespaces;
        }
        const data = await client.graphql<{
          webhookSubscriptionCreate: {
            webhookSubscription: WebhookSubscriptionNode | null;
            userErrors: ShopifyUserError[];
          };
        }>(WEBHOOK_CREATE_MUTATION, {
          topic: args.topic,
          webhookSubscription,
        });
        throwIfUserErrors(
          data.webhookSubscriptionCreate.userErrors,
          "webhookSubscriptionCreate",
        );
        const w = data.webhookSubscriptionCreate.webhookSubscription;
        if (!w) {
          return {
            content: [
              { type: "text" as const, text: "webhookSubscriptionCreate returned no subscription." },
            ],
          };
        }
        return {
          content: [
            {
              type: "text" as const,
              text: `Created webhook ${w.topic} → ${w.endpoint.callbackUrl ?? "(endpoint)"} — ${w.id}`,
            },
          ],
        };
      },
    );
  • Input schema for the create_webhook tool: topic (string), callbackUrl (URL), format (JSON/XML), includeFields (optional string array), metafieldNamespaces (optional string array).
    const createWebhookSchema = {
      topic: z
        .string()
        .describe(
          "WebhookSubscriptionTopic, e.g. 'ORDERS_CREATE', 'ORDERS_PAID', 'PRODUCTS_UPDATE', 'INVENTORY_LEVELS_UPDATE', 'APP_UNINSTALLED'. See Shopify docs for full list.",
        ),
      callbackUrl: z
        .string()
        .url()
        .describe("HTTPS endpoint that will receive the webhook POSTs."),
      format: z.enum(["JSON", "XML"]).default("JSON"),
      includeFields: z
        .array(z.string())
        .optional()
        .describe(
          "Optional: only include these fields in the payload (reduces payload size).",
        ),
      metafieldNamespaces: z
        .array(z.string())
        .optional()
        .describe(
          "Optional: include metafields from these namespaces in the payload.",
        ),
    };
  • The registerWebhookTools function registers all webhook tools (including 'create_webhook') on the MCP server.
    export function registerWebhookTools(
      server: McpServer,
      client: ShopifyClient,
    ): void {
      server.tool(
        "list_webhooks",
        "List webhook subscriptions on the store. Each subscription wires a Shopify event topic (ORDERS_CREATE, PRODUCTS_UPDATE, INVENTORY_LEVELS_UPDATE, etc.) to a delivery target — typically an HTTPS callback URL, but Pub/Sub and EventBridge are also supported. Returns each subscription's topic, delivery format (JSON/XML), endpoint, API version, and any field/metafield filters applied. Filter by topic to scope the result. Use this to audit existing automation hooks before creating new ones.",
        listWebhooksSchema,
        async (args) => {
          const data = await client.graphql<{
            webhookSubscriptions: Connection<WebhookSubscriptionNode>;
          }>(LIST_WEBHOOKS_QUERY, {
            first: args.first,
            after: args.after,
            topics: args.topics,
          });
          const edges = data.webhookSubscriptions.edges;
          if (edges.length === 0) {
            return {
              content: [
                { type: "text" as const, text: "No webhook subscriptions." },
              ],
            };
          }
          return {
            content: [
              {
                type: "text" as const,
                text: [
                  `Found ${edges.length} webhook subscription(s):`,
                  ...edges.map(({ node }) => summarizeWebhook(node)),
                ].join("\n"),
              },
            ],
          };
        },
      );
    
      server.tool(
        "get_webhook",
        "Fetch a single webhook subscription's full configuration by GID — topic, endpoint, format, API version, includeFields filter, metafield namespaces, and timestamps. Use to verify subscription details before update or delete, or when debugging delivery issues.",
        getWebhookSchema,
        async (args) => {
          const data = await client.graphql<{
            webhookSubscription: WebhookSubscriptionNode | null;
          }>(GET_WEBHOOK_QUERY, { id: args.id });
          if (!data.webhookSubscription) {
            return {
              content: [
                { type: "text" as const, text: `Webhook not found: ${args.id}` },
              ],
            };
          }
          const w = data.webhookSubscription;
          return {
            content: [
              {
                type: "text" as const,
                text: [
                  summarizeWebhook(w),
                  `  Created: ${w.createdAt}`,
                  `  Updated: ${w.updatedAt}`,
                ].join("\n"),
              },
            ],
          };
        },
      );
    
      server.tool(
        "create_webhook",
        "Subscribe to a Shopify event topic and have payloads POSTed to your HTTPS endpoint. Common topics: ORDERS_CREATE, ORDERS_PAID, ORDERS_FULFILLED, PRODUCTS_CREATE, PRODUCTS_UPDATE, INVENTORY_LEVELS_UPDATE, CUSTOMERS_CREATE, APP_UNINSTALLED. Use includeFields to receive only specific fields in the payload (reduces bandwidth and avoids leaking unrelated data). metafieldNamespaces opts in to including metafields from the listed namespaces. callbackUrl must be HTTPS in production. Returns the new subscription's GID; verify delivery is working with a few real events before relying on it.",
        createWebhookSchema,
        async (args) => {
          const webhookSubscription: Record<string, unknown> = {
            callbackUrl: args.callbackUrl,
            format: args.format,
          };
          if (args.includeFields) {
            webhookSubscription.includeFields = args.includeFields;
          }
          if (args.metafieldNamespaces) {
            webhookSubscription.metafieldNamespaces = args.metafieldNamespaces;
          }
          const data = await client.graphql<{
            webhookSubscriptionCreate: {
              webhookSubscription: WebhookSubscriptionNode | null;
              userErrors: ShopifyUserError[];
            };
          }>(WEBHOOK_CREATE_MUTATION, {
            topic: args.topic,
            webhookSubscription,
          });
          throwIfUserErrors(
            data.webhookSubscriptionCreate.userErrors,
            "webhookSubscriptionCreate",
          );
          const w = data.webhookSubscriptionCreate.webhookSubscription;
          if (!w) {
            return {
              content: [
                { type: "text" as const, text: "webhookSubscriptionCreate returned no subscription." },
              ],
            };
          }
          return {
            content: [
              {
                type: "text" as const,
                text: `Created webhook ${w.topic} → ${w.endpoint.callbackUrl ?? "(endpoint)"} — ${w.id}`,
              },
            ],
          };
        },
      );
    
      server.tool(
        "update_webhook",
        "Modify an existing webhook subscription's callback URL, payload format, includeFields filter, or metafield-namespace filter. Topic cannot be changed — to switch event types, delete and recreate the subscription. Use when migrating an endpoint to a new domain, switching from JSON to XML, or tightening payload size by adding includeFields. Omitted parameters are left unchanged.",
        updateWebhookSchema,
        async (args) => {
          const webhookSubscription: Record<string, unknown> = {};
          if (args.callbackUrl !== undefined) {
            webhookSubscription.callbackUrl = args.callbackUrl;
          }
          if (args.format !== undefined) webhookSubscription.format = args.format;
          if (args.includeFields !== undefined) {
            webhookSubscription.includeFields = args.includeFields;
          }
          if (args.metafieldNamespaces !== undefined) {
            webhookSubscription.metafieldNamespaces = args.metafieldNamespaces;
          }
    
          const data = await client.graphql<{
            webhookSubscriptionUpdate: {
              webhookSubscription: WebhookSubscriptionNode | null;
              userErrors: ShopifyUserError[];
            };
          }>(WEBHOOK_UPDATE_MUTATION, {
            id: args.id,
            webhookSubscription,
          });
          throwIfUserErrors(
            data.webhookSubscriptionUpdate.userErrors,
            "webhookSubscriptionUpdate",
          );
          const w = data.webhookSubscriptionUpdate.webhookSubscription;
          return {
            content: [
              {
                type: "text" as const,
                text: w
                  ? `Updated webhook ${w.topic} → ${w.endpoint.callbackUrl ?? "(endpoint)"} — ${w.id}`
                  : `Updated webhook ${args.id}.`,
              },
            ],
          };
        },
      );
    
      server.tool(
        "delete_webhook",
        "Permanently unsubscribe from an event topic by deleting the webhook subscription. Stops all future deliveries to that endpoint for that topic — irreversible (you'd have to re-create with create_webhook). Use when retiring an integration or switching topics. Returns the deleted GID, or a no-op message if nothing matched.",
        deleteWebhookSchema,
        async (args) => {
          const data = await client.graphql<{
            webhookSubscriptionDelete: {
              deletedWebhookSubscriptionId: string | null;
              userErrors: ShopifyUserError[];
            };
          }>(WEBHOOK_DELETE_MUTATION, { id: args.id });
          throwIfUserErrors(
            data.webhookSubscriptionDelete.userErrors,
            "webhookSubscriptionDelete",
          );
          return {
            content: [
              {
                type: "text" as const,
                text: data.webhookSubscriptionDelete.deletedWebhookSubscriptionId
                  ? `Deleted webhook ${data.webhookSubscriptionDelete.deletedWebhookSubscriptionId}.`
                  : "No webhook matched; nothing deleted.",
              },
            ],
          };
        },
      );
    }
  • The GraphQL mutation (WEBHOOK_CREATE_MUTATION) used by the create_webhook handler to create a webhook subscription.
    const WEBHOOK_CREATE_MUTATION = /* GraphQL */ `
      mutation WebhookCreate(
        $topic: WebhookSubscriptionTopic!
        $webhookSubscription: WebhookSubscriptionInput!
      ) {
        webhookSubscriptionCreate(
          topic: $topic
          webhookSubscription: $webhookSubscription
        ) {
          webhookSubscription {
            id
            topic
            format
            endpoint {
              __typename
              ... on WebhookHttpEndpoint { callbackUrl }
            }
          }
          userErrors { field message }
        }
      }
    `;
  • src/server.ts:22-66 (registration)
    Import and invocation of registerWebhookTools in the main server setup, where the tools are registered on the MCP server instance.
    import { registerWebhookTools } from "./tools/webhooks.js";
    import { registerMetaobjectTools } from "./tools/metaobjects.js";
    import { registerAnalyticsTools } from "./tools/analytics.js";
    import { registerBridgeTools } from "./tools/bridge.js";
    
    export interface ServerConfig {
      host: string;
      port: number;
      shopifyStore: string;
      shopifyAccessToken: string;
      shopifyApiVersion?: string;
      comfyUIUrl?: string;
      comfyUIPublicUrl?: string;
      comfyUIDefaultCkpt: string;
    }
    
    interface Session {
      server: McpServer;
      transport: StreamableHTTPServerTransport;
    }
    
    function buildContext(config: ServerConfig) {
      const shopify = new ShopifyClient({
        store: config.shopifyStore,
        accessToken: config.shopifyAccessToken,
        apiVersion: config.shopifyApiVersion,
      });
      const comfyui = config.comfyUIUrl
        ? new ComfyUIClient({
            baseUrl: config.comfyUIUrl,
            publicUrl: config.comfyUIPublicUrl ?? config.comfyUIUrl,
          })
        : null;
      const buildServer = () => {
        const s = new McpServer({ name: "shopify-mcp", version: "0.1.0" });
        registerProductTools(s, shopify);
        registerOrderTools(s, shopify);
        registerInventoryTools(s, shopify);
        registerCustomerTools(s, shopify);
        registerMetafieldTools(s, shopify);
        registerDraftOrderTools(s, shopify);
        registerCollectionTools(s, shopify);
        registerVariantTools(s, shopify);
        registerFulfillmentTools(s, shopify);
        registerWebhookTools(s, shopify);
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, description carries full burden. It discloses creation behavior, that payloads are POSTed, returns a GID, and recommends verification. Mentions security (HTTPS) and data minimization (includeFields). Lacks details on idempotency or rate limits, but covers key behavioral traits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four sentences, each earning its place: first states purpose, second lists topics, third explains options, fourth adds security and verification. No fluff, front-loaded, and well-structured for quick parsing.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema, but description mentions return is GID and advises verification, partially compensating. Covers all parameters and gives usage context. Lacks error handling or failure scenarios, but adequate for typical use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 80%, so baseline is 3. Description adds value beyond schema by explaining includeFields reduces bandwidth/leakage, metafieldNamespaces includes metafields, and callbackUrl must be HTTPS. Provides concrete topic examples not in schema, enhancing parameter understanding.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description uses specific verb 'Subscribe' and resource 'Shopify event topic', clearly stating the tool creates a webhook subscription. Examples of common topics further clarify purpose and distinguish from siblings like delete_webhook or list_webhooks.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit context on when to use: subscribing to event topics. Includes practical tips like using includeFields to reduce bandwidth and metafieldNamespaces, and requiring HTTPS in production. Does not explicitly contrast with update_webhook or state when not to use, but still offers strong guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/miller-joe/shopify-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server