Skip to content

Search the documentation by title, section, or page text.

Connect to Shopify

Connect your Shopify store so purchases made through a Shopify checkout show up in Botsi. When an order is paid or refunded, Shopify sends a webhook to an endpoint Botsi hosts. Botsi records first subscription charges, renewals, free trials and their conversion to paid, one-time purchases, and refunds. It matches each purchase to a Profile using a cart attribute that your web paywall sets.

Shopify purchases don't grant access

Shopify records revenue for revenue reporting only. Its purchases don't change subscriber status or Access Levels. See Notification URLs.

What Botsi records#

Shopify orderRecorded in Botsi as
A one-time product is paidOne-time purchase
A subscription is bought with a payment todaySubscription purchase
A subscription is bought with a free first period ($0 order)Trial started
The first paid renewal after a free trialTrial converted
Any other paid subscription renewalSubscription renewal
An order is refunded, fully or partlyRefund of that purchase (negative revenue)

Botsi uses two Shopify webhook topics: orders/paid and refunds/create. Cancellations, pauses, expirations, and failed renewal payments create no order, so Botsi does not see them.

Creating the Shopify app#

Shopify signs every webhook with the client secret of the app that subscribed to it. So you need a Shopify app installed on your store that subscribes to Botsi's webhook URL. Your store's own custom app is enough. It needs no storefront or admin UI.

  1. In the Botsi dashboard at https://app.botsi.com, open App Settings → Shopify and copy the Webhook URL.
  2. In the Shopify Dev Dashboard, create an app for your store, or open an existing one.
  3. Give the app the read_orders access scope.
  4. Add a webhook subscription with these settings:
    • Topics: orders/paid and refunds/create
    • API version: 2026-10 or later
    • URL: the Webhook URL from step 1
  5. Install the app on your store.

Order webhooks contain customer data. If Shopify asks for protected customer data access when you add the subscription, request access to order data for app functionality.

Adding your shop and signing secret#

  1. In the Dev Dashboard, open your app's Settings and copy the Client secret.
  2. In Botsi, go to App Settings → Shopify.
  3. Enter your store's myshopify.com domain as the Production shop domain. For example, my-store.myshopify.com. Use this domain, not a custom domain.
  4. Paste the client secret into Production signing secret.
  5. Turn on Enable Shopify webhooks and save.

Secrets are write-only. After you save, Botsi does not show them again. To keep the saved secret, leave the field blank when you save other changes. If you rotate the client secret in Shopify, paste the new one. Clearing a shop domain also removes its secret.

Botsi answers Shopify with an error only for temporary problems, which Shopify retries for up to 4 hours. Deliveries that Botsi ignores on purpose, such as a free order, are acknowledged so that Shopify does not retry them.

Testing with a sandbox shop#

App Settings → Shopify also has an optional Sandbox shop domain and Sandbox signing secret. Use them for a Shopify development store with its own app. Every order from the sandbox shop is recorded as sandbox revenue, and sandbox revenue never appears in production reporting. Both shops send to the same Webhook URL. Botsi tells them apart by the shop that sent each delivery.

On the production shop, Botsi records an order as production revenue unless Shopify marks it as a test order. For example, an order paid with a test payment gateway is a test order.

Mapping products#

A Botsi product matches one Shopify variant sold one way: either as a one-time purchase, or under one subscription selling plan (the billing schedule a customer subscribes with, managed by your subscriptions app). In the Botsi product form, open the Shopify tab:

FieldValue
Shopify Variant IDThe numeric ID of the product variant. For example, 44123456789012. It is the number at the end of the variant's URL in the Shopify admin (…/variants/44123456789012).
Selling Plan IDLeave blank for a one-time purchase. For a subscription, enter the numeric ID of the selling plan the buyer subscribes with. For example, 691234567890.

Enter the numbers only, not the gid://shopify/… form. To sell one variant both one-time and as a subscription, create one Botsi product per selling plan, plus one with the Selling Plan ID left blank for the one-time purchase.

Matching purchases to Profiles#

Botsi links a Shopify order to a Profile through a cart attribute named botsi_profile_id. Set it to the Botsi Profile ID when your web paywall creates the cart. This is the profileId Botsi returns when you create a Profile, not your appUserId. Shopify copies cart attributes onto the order, and onto every renewal order of a subscription.

Creating a cart with the Storefront API#

const profileId = '0072102a-c00c-4ea5-9271-1b6e975f2d63'; // the Profile's Botsi ID, not appUserId

const query = `
  mutation ($input: CartInput!) {
    cartCreate(input: $input) {
      cart { checkoutUrl }
      userErrors { field message }
    }
  }
`;

const response = await fetch('https://my-store.myshopify.com/api/2026-10/graphql.json', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Shopify-Storefront-Access-Token': process.env.SHOPIFY_STOREFRONT_TOKEN,
  },
  body: JSON.stringify({
    query,
    variables: {
      input: {
        lines: [
          {
            merchandiseId: 'gid://shopify/ProductVariant/44123456789012',
            sellingPlanId: 'gid://shopify/SellingPlan/691234567890', // omit for a one-time purchase
            quantity: 1,
          },
        ],
        attributes: [{ key: 'botsi_profile_id', value: profileId }],
      },
    },
  }),
});

const { data } = await response.json();
// Send the buyer to data.cartCreate.cart.checkoutUrl

Setting the attribute with the AJAX Cart API#

If your paywall uses the Online Store's AJAX Cart API instead, set the same attribute with POST /cart/update.js before sending the buyer to checkout:

await fetch('/cart/update.js', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    attributes: { botsi_profile_id: '0072102a-c00c-4ea5-9271-1b6e975f2d63' },
  }),
});

Once a subscription's first order is matched, its renewals are linked to the same Profile, even if a renewal order is missing the attribute. If no Profile matches, the revenue is still recorded, without a Profile.

How orders are classified#

QuestionHow Botsi decides
Subscription or one-timeAn order is a subscription order when a line item was bought under a selling plan. Otherwise it is a one-time purchase.
First purchase or renewalOrders that your subscriptions app creates when it bills a subscription are renewals. Orders placed at checkout are first purchases.
Free trialsA subscription checkout with a $0 total is a trial start. The first paid renewal after it is a trial conversion. Later renewals are plain renewals. $0 renewals during a free period are not recorded. Your subscriptions app decides when the paid price starts.
AmountsRevenue is the order total in the currency the buyer paid, converted to USD. Proceeds equal revenue, because Shopify Payments fees are not part of the webhook.
RefundsA refund is recorded as negative revenue on the purchase it refunds, using the refund's successful transactions. Each partial refund is recorded separately. A refund of an order Botsi never recorded is ignored.
RetriesA retried webhook does not double-count. Botsi records each order and each refund once.

Verifying the connection#

The Connection section of App Settings → Shopify shows the status and when the last event was received:

StatusMeaning
Setup incompleteNo shop has both a domain and a signing secret.
DisabledA shop is configured, but Enable Shopify webhooks is off. Deliveries are acknowledged but not recorded.
Awaiting first eventEnabled, with no verified delivery yet.
Receiving eventsAt least one verified delivery has arrived.

To test, place an order on your sandbox shop and check that the status changes to Receiving events. The purchase then appears in Botsi as sandbox revenue.

Responses#

StatusWhen
200The delivery was received, including orders Botsi ignores on purpose
400The body isn't JSON
401The signature doesn't verify, or the shop isn't configured in Botsi
5xxA temporary failure. Shopify retries 8 times over 4 hours

The responses every notification URL shares also apply. See Notification URLs.

Troubleshooting#

SymptomCheck
Status stays Awaiting first eventThe app is installed on the shop, the webhook subscription points at the Webhook URL from Botsi, and an order has been paid since.
Shopify's delivery log shows 401The shop domain in Botsi is the shop's myshopify.com domain, and the signing secret is the current client secret of the app that owns the webhook subscription.
Subscriptions are missing, or every order looks one-timeThe webhook subscription uses API version 2026-10 or later.
Revenue has no productThe Shopify Variant ID and Selling Plan ID in the Botsi product match the order's line item exactly. A blank Selling Plan ID matches one-time purchases only.
Revenue has no ProfileThe cart has a botsi_profile_id attribute set to the Profile's Botsi ID, not your appUserId.