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#
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.
- In the Botsi dashboard at https://app.botsi.com, open App Settings → Shopify and copy the Webhook URL.
- In the Shopify Dev Dashboard, create an app for your store, or open an existing one.
- Give the app the
read_ordersaccess scope. - Add a webhook subscription with these settings:
- Topics:
orders/paidandrefunds/create - API version:
2026-10or later - URL: the Webhook URL from step 1
- Topics:
- Install the app on your store.
Orders sent with an API version before 2026-10 are ignored
From version 2026-10, order webhooks carry the selling plan of each line item. That is how Botsi tells subscriptions from one-time purchases. Botsi ignores orders delivered with an older API version rather than record subscriptions as one-time purchases.
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#
- In the Dev Dashboard, open your app's Settings and copy the Client secret.
- In Botsi, go to App Settings → Shopify.
- Enter your store's
myshopify.comdomain as the Production shop domain. For example,my-store.myshopify.com. Use this domain, not a custom domain. - Paste the client secret into Production signing secret.
- 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:
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.checkoutUrlSetting 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#
Verifying the connection#
The Connection section of App Settings → Shopify shows the status and when the last event was received:
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#
The responses every notification URL shares also apply. See Notification URLs.