How to build a subscription store: Connecting Stripe to Shopify with Cloudhooks

While Shopify excels at one-time purchases, businesses often need Stripe's subscription features. Using Cloudhooks, you can combine both platforms' strengths without complex development.

In this tutorial, you'll learn how to set up Stripe subscriptions that automatically create corresponding orders in your Shopify store, giving you the best of both platforms: Stripe's subscription management and Shopify's order processing capabilities.

Time to complete: 1.5 hours
Difficulty level: Intermediate

What you'll build

In this tutorial, you'll create an automation that:

  1. Processes subscription payments through Stripe
  2. Automatically creates corresponding orders in Shopify
  3. Maintains subscription status and customer data across both platforms
  4. Handles recurring billing without manual intervention

The Flow

  1. Customer subscribes: Customer signs up for a subscription through your Stripe checkout or payment link
  2. Stripe processes payment: Stripe handles the recurring billing and payment processing
  3. Webhook triggers: When a payment succeeds, Stripe sends a webhook event
  4. Cloudhooks processes: Your hook receives the event and creates a corresponding order in Shopify
  5. Order created: The order appears in your Shopify admin with all relevant subscription details

Key Benefits

  • Automated order creation: No manual entry required for subscription orders
  • Reliable tracking: Every successful subscription payment creates a Shopify order
  • Centralized management: View all orders in Shopify while managing subscriptions in Stripe
  • Flexible integration: Customize the integration to match your business needs

Prerequisites

  • A Shopify store with Cloudhooks installed
  • An activated Stripe account with admin or developer access
  • Basic understanding of subscription business models

Set up Stripe

We'll configure Stripe to handle your subscription products and payments. This includes creating products, setting up payment forms, and configuring webhooks for automated synchronization.

Create subscription products

First, we'll set up your subscription products in Stripe. We recommend using test mode initially to verify your integration without processing real transactions.

  1. Log into your Stripe dashboard
  2. Enable test mode using the toggle in the top-right corner

đź’ˇ Tip: While we're using test mode, all steps are identical in live mode. Always test thoroughly before switching to production.

Add your products

Follow these steps to create your subscription products:

1) Select “Product catalogue” from the main navigation on the Stripe dashboard:

2) Add your recurring products by entering name, description, image, billing period and price:

3) Add as many product as needed for your implementation:

Important configuration notes

  • We're creating a coffee subscription with dark and light roast variants as an example.
  • Each product needs a recurring price configured.
  • Save your product IDs (starting with "prod_") for later use in the hook configuration.
  • Consider your billing cycle (monthly, quarterly, annual) carefully.

đź’ˇ Best Practice: Create separate products for different subscription variants rather than using product options.

Set up payment collection

Next, we'll create a way for customers to subscribe to your products. Stripe offers several options:

  • Stripe Checkout: A hosted payment page
  • Payment Elements: Embedded forms for your website
  • Payment Links: Shareable links for any channel

For this guide, we'll use Payment Links as they're the quickest to set up and can be shared anywhere.

Create a Payment Link

Follow these steps to create your subscription payment link:

1) Select the Payment Links menu item from the “Product” main menu:

2) Create a payment link by selecting your products and configuring basic settings like promotion codes and post-payment actions:

3) Create separate payment links for each product line - while you can sell multiple subscription variations in one link, dedicated links are easier to manage and track:

đź’ˇ Important: Take note of the payment links as these are the links that you can distribute on your marketing channels (including your website).

Configure the Customer Portal

A successful subscription business needs to provide customers with self-service options to:

  • Manage their payment methods
  • Update billing information
  • View subscription history
  • Cancel or modify subscriptions

Stripe's Customer Portal handles all these requirements out of the box, reducing your customer support workload.

Enable the Customer Portal

Follow these steps to activate and customize your portal:

1) Go to “Settings / Billing / Customer Portal” and press the “Activate link” button in the “Launch customer portal with a link“ section:

2) Embed the generated link into your website.

3) Customize the portal settings to control:

  • Invoice visibility and history
  • Customer data collection fields:
    • Name
    • Phone number
    • Billing address
    • Shipping address
    • Tax ID
  • Available payment methods
  • Subscription cancellation permissions

Customers can use the portal by entering the email address used for the subscription:

After login, customers can • Manage payment methods • Update billing and shipping addresses • View billing history:

Set up webhooks

The final piece of our Stripe configuration is webhooks, which will notify Cloudhooks whenever a subscription payment succeeds. This automated communication ensures that Shopify orders are created immediately after successful payments.

Configure webhook endpoints

Before setting up webhooks in Stripe, you'll need your Cloudhooks endpoint URL. We'll create this in two phases:

Phase 1: Get your Cloudhooks endpoint

  1. Create your hook in Cloudhooks first (detailed in the "Set up Cloudhooks" section below)
  2. Copy the generated endpoint URL
  3. Keep this URL handy for the next phase

Phase 2: Configure Stripe webhooks

1) In the Stripe dashboard, go to “Developers / Webhooks” and press “Add destination”:

3) Configure the webhook destination on the wizard pages.

On the first page, set fields as follows:

  • Events from: Your account
  • Payload style: Snapshot
  • API version: Latest
  • Events: invoice.paid

On the second page, select “Webhook endpoint” as the destination type:

On the final page, enter your Cloudhooks endpoint URL and click "Create endpoint":

Configure Shopify

While Stripe handles the subscription payments, Shopify needs corresponding products to generate orders. We'll set up both products and the necessary data structure to track subscription information.

Create matching products

First, we'll create products in Shopify that mirror your Stripe subscription offerings. This ensures proper order creation when payments are processed.

Set up product variants

Follow these steps to create a product with variants that match your Stripe subscriptions:

1) Navigate to “Products” and click "Add product" in the top-right corner:

2) Configure your product variants:

  • Add a variant called "Roast type"
  • Create options for different roasts (e.g., dark, light)
  • Set prices to match your Stripe subscription prices

3) Record product and variant IDs:

  • Click the variant name
  • Note the IDs from the URL (as shown below)
  • Save these for your hook configuration

đź’ˇ Tip: Create a spreadsheet mapping Stripe product IDs to Shopify variant IDs for easy reference.

Set up metafields

When integrating two systems in a way we’re doing here, it’s always an important consideration to keep track of identifiers of the artifacts in the other system.

In our case, we’d like to know the Stripe subscription, customer, product and charge IDs.

We’ll keep track of these by adding metafields to orders, and our hook code in Cloudhooks will extract this data from the payload and fill in these fields of the order it creates in Shopify.

Follow these steps:

1) Go to the “Settings / Custom data” page and select “Orders” in the “Metafield definitions” pane.

2) Press "Add definition" and create the following one-line text fields:

  • Stripe Customer ID
  • Stripe Product ID
  • Stripe Subscription ID
  • Stripe Charge ID

3) Once you've added all four metafields, your configuration should appear as shown:

If you’d like to make sure that your setup works with the hook example out of the box, then use the following namespace and key for the fields:

  • Stripe customer id: custom.stripeCustomerId
  • Stripe product id: custom.stripeProductId
  • Stripe subscription id: custom.stripeSubscriptionId
  • Stripe charge id: custom.stripeChargeId

Protect your subscription products

Since subscriptions are managed through Stripe, you'll want to prevent direct purchases through Shopify's checkout:

Option 1: Unpublish products

  • Navigate to product settings
  • Set status to "Unpublished"
  • Products remain available for order creation but hidden from customers

Option 2: Create a hidden collection

  • Create a new collection
  • Add subscription products
  • Set collection visibility to "Hidden"
  • Use for inventory management only

đź’ˇ Best Practice: Always prevent duplicate purchase paths to avoid customer confusion and billing complications.

Set up Cloudhooks

Create an API key

First, we'll create an API key to secure the communication between Stripe and Cloudhooks.

1) Navigate to the Cloudhooks dashboard

2) Click "Settings" in the main navigation:

3) In the "Access tokens" section:

  • Click "Generate new"
  • Name your token (e.g., "Stripe-Shopify Integration")
  • Click "Generate"

3) Secure your credentials:

  • Copy the generated token immediately
  • Store it in a password manager
  • Note: This token will only be shown once

⚠️ Security note: Protect this API key as it provides access to your Cloudhooks endpoint.

Configure the hook

Now we'll create the automation that processes Stripe webhook events and creates Shopify orders. This hook will:

  • Receive Stripe webhook events
  • Extract subscription and customer data
  • Create corresponding orders in Shopify
  • Store Stripe reference IDs in order metafields

Create the hook

Now we'll create a hook that processes Stripe webhook events and creates Shopify orders. This hook will handle the core integration logic between the two platforms.

Initialize the hook

1) On the Cloudhooks dashboard, click "Create hook":

2) In the "Trigger" tab:

  • Choose “External” trigger type
  • Click “Select trigger”
  • Select the following authentication parameters:‍
    • Authentication method: API key‍
    • Parameter type: Query parameter‍
    • Parameter name: x-authentication

đź’ˇ Tip: Using query parameters makes testing easier with tools like cURL or Postman.

Set up webhook endpoint

1) Save the initial configuration

2) Note your webhook URL on the “Trigger” tab:

3) Construct your final webhook URL:

https://api.cloudhooks.dev/api/v1/hooks/[hook-id]/run?x-authentication=[your-api-key]

Add the integration code

1) Switch to the "Hook" tab

2) Insert the hook code (provided in the "Hook code" section below)

3) Update the product mapping:

const productVariantMap = [
  { 
    stripeProductId: "your_stripe_product_id_1", 
    shopifyVariantId: "your_shopify_variant_id_1" 
  },
  { 
    stripeProductId: "your_stripe_product_id_2", 
    shopifyVariantId: "your_shopify_variant_id_2" 
  }
];

Activate the integration

1) Go to the “Settings” tab

2) Set a descriptive name (e.g., "Stripe subscription to Shopify order")

3) Toggle the status to "Active"

4) Save your changes

⚠️ Important: Keep your webhook URL (including API key) secure as it provides direct access to your integration.

Testing your integration

Before going live, it's crucial to thoroughly test your integration. We'll explore two testing methods to ensure everything works as expected.

Method 1: Test with Stripe's test mode

This approach lets you test the complete flow without processing real payments:

  1. Ensure Stripe is in test mode
  2. Create a test subscription:
    • Use Stripe's test card: 4242 4242 4242 4242
    • Set any future expiry date
    • Use any CVC
  3. Verify the integration:
    • Check Cloudhooks logs for webhook receipt
    • Verify order creation in Shopify
    • Confirm all metadata is correctly populated

đź’ˇ Tip: Use different test cards to simulate various scenarios (successful payments, failed payments, etc.)

Method 2: Webhook simulation

Test the webhook processing without creating test subscriptions:

1) In Cloudhooks, go to your hook's "Test" tab

2) Use a customized payload of the "invoice.paid" event (you can get a test payload using Stripe CLI)

3) Click "Test" to simulate the webhook

4) Verify the order creation in Shopify

Hook code

// Define a lookup array for matching Stripe products to Shopify variants
const productVariantMap = [
  { 
    stripeProductId: "your_stripe_product_id_1", 
    shopifyVariantId: "your_shopify_variant_id_1" 
  },
  { 
    stripeProductId: "your_stripe_product_id_2", 
    shopifyVariantId: "your_shopify_variant_id_2" 
  }
];


module.exports = async function(payload, actions, context) {

    if (payload.type !== "invoice.paid") {
        console.log(`Ignoring payload with type: ${payload.type}`);
        return;
    }

    const productId = payload.data.object.lines.data[0].price.product;
    const productMapping = productVariantMap.find(mapping => mapping.stripeProductId === productId);
    if (!productMapping) {
        console.log(`No Shopify variant found for Stripe product: ${productId}`);
        return;
    }

    const customerId = payload.data.object.customer;
    const subscriptionId = payload.data.object.subscription;
    const chargeId = payload.data.object.charge;
    const customerEmail = payload.data.object.customer_email;
    const customerFirstName = payload.data.object.customer_name.split(' ')[0];
    const customerLastName = payload.data.object.customer_name.split(' ')[1];
    const grossPaymentAmount = payload.data.object.total;

    const createOrderQuery = `mutation OrderCreate($order: OrderCreateOrderInput!, $options: OrderCreateOptionsInput) {
      orderCreate(order: $order, options: $options) {
        order {
          id
          name
          email
          customer {
            id
            displayName
          }
          tags
          note
        }
        userErrors {
          field
          message
        }
      }
    }`;
    
    const createOrderData = {
      "order": {
        "lineItems": [
          {
            "variantId": `gid://shopify/ProductVariant/${productMapping.shopifyVariantId}`,
            "quantity": 1
          }
        ],
        "email": `${customerEmail}`,
        "customer": {
          "toUpsert": {
            "email": `${customerEmail}`,
            "firstName": `${customerFirstName}`,
            "lastName": `${customerLastName}`
          }
        },
        "note": "Created via Stripe payment integration",
        "tags": ["stripe"]
      }
    };
    

    try {
        const createOrderResponse = await actions.shopify.graphql(
          '/admin/api/2025-01/graphql.json',
          {
            query: createOrderQuery, 
            variables: createOrderData
          }
        );

        const order = createOrderResponse.data.orderCreate.order;
        if (!order) {
            console.error("Order creation failed:", createOrderResponse.data.orderCreate.userErrors);
            return;
        }

        // Add metafields to the created order
        const metafieldsMutation = `
            mutation AddMetafields {
                metafieldsSet(metafields: [
                    {
                        namespace: "custom",
                        key: "stripeCustomerId",
                        value: "${customerId}",
                        type: "single_line_text_field",
                        ownerId: "${order.id}"
                    },
                    {
                        namespace: "custom",
                        key: "stripeSubscriptionId",
                        value: "${subscriptionId}",
                        type: "single_line_text_field",
                        ownerId: "${order.id}"
                    },
                    {
                        namespace: "custom",
                        key: "stripeChargeId",
                        value: "${chargeId}",
                        type: "single_line_text_field",
                        ownerId: "${order.id}"
                    },
                    {
                        namespace: "custom",
                        key: "stripeProductId",
                        value: "${productId}",
                        type: "single_line_text_field",
                        ownerId: "${order.id}"
                    }
                ]) {
                    userErrors {
                        field
                        message
                    }
                }
            }
        `;

        const metafieldsResponse = await actions.shopify.graphql(metafieldsMutation);

        if (metafieldsResponse.data.metafieldsSet.userErrors.length > 0) {
            console.error("Failed to set metafields:", metafieldsResponse.data.metafieldsSet.userErrors);
        } else {
            console.log("Metafields successfully added to order:", order.id);
        }
    } catch (error) {
        console.error(`Error during order creation or metafield addition:`, error);
    }
};

Extending the integration

Additional webhook events

While invoice.paid handles the recurring payment case, consider listening to these additional events for a complete subscription lifecycle:

  • customer.subscription_created: Track new subscriptions
  • customer.subscription_updated: Handle plan changes
  • customer.subscription_deleted: Process cancellations
  • invoice.payment_failed: Handle failed payments

Revenue recovery

Implementing Stripe's revenue recovery features is crucial for maintaining healthy subscription revenue. Payment failures are common and can occur due to:

  • Technical issues with payment processors
  • Expired or replaced cards
  • Temporary bank declines
  • Network communication errors

Without proper recovery mechanisms, these failures lead to unnecessary subscription cancellations. Studies show that 20-40% of churn is involuntary - customers didn't actively choose to cancel.

Configure these recovery features in Stripe:

  1. Configure retry rules:
    • Set retry attempt count
    • Define retry intervals
    • Customize dunning emails
  2. Enable card expiration notifications:
    • Automatic reminders before expiry
    • Custom email templates
    • Update card prompts

đź’ˇ Tip: Access these settings in Stripe under Settings > Billing > Revenue Recovery

Troubleshooting

Common Issues

  1. Webhook not receiving events
    • Verify webhook URL is correct in Stripe
    • Check API key is properly included in URL
    • Confirm hook is set to "Active" in Cloudhooks
  2. Orders not creating in Shopify
    • Verify product variant mapping is correct
    • Check Shopify API access is working
    • Confirm webhook payload contains expected data
  3. Missing metadata
    • Ensure all metafields are properly defined in Shopify
    • Check metafield namespace and keys match hook code
    • Verify Stripe is sending complete customer data

Debugging Tips

  • Use Cloudhooks logs to monitor webhook receipts
  • Enable Stripe webhook logs for delivery confirmation
  • Test with Stripe's webhook test events

‍