Skip to main content

Sell Subi subscriptions on a Lovable or Replit storefront

If your storefront is built in Lovable, Replit or another AI site builder and sells through Shopify's Storefront API, your Shopify theme does not show on it, and neither does the Subi widget in that theme. Your Subi subscribe-and-save plans and memberships still work there. Subi puts each plan on your Shopify products as a selling plan, and Shopify's checkout creates the subscription when a cart line carries one. So the site only has to read the plans and send them with the cart. This page gives you a prompt that has the builder's agent do that. If you would rather have it prepared for you, tell us what you want to connect and we prepare it for your store on any paid Subi plan.

Before you start

  • Subi is installed on the store, and each product you want to sell as a subscription or a membership has a Subi plan. The site shows and charges exactly what those plans say, so check their names and prices in Subi first.

  • The products are published to the sales channel your site's Storefront API access belongs to, and that channel's Storefront API permissions include selling plans. Without that permission, the products come back with no plans.

  • Copy your customer portal link in Subi under Storefront & Portal > Customer Portal. The prompt asks for it.

The prompt

Paste everything in the box into Lovable's chat or into Replit Agent, with your store's myshopify domain and your portal link filled in on the first two lines. The same prompt works in both.

STORE DOMAIN: <your-store>.myshopify.com
PORTAL LINK: <the link you copied under Storefront & Portal > Customer Portal in Subi>This site sells through Shopify's Storefront API. The store uses the Subi app for subscriptions and memberships. Subi puts its plans on the Shopify products as selling plans, and Shopify's checkout creates the subscription when a cart line carries one. Make the site sell them. Never hardcode a plan id or a plan price.1. Read the plans. Wherever the site fetches products from the Storefront API, add these fields to the product query:
   requiresSellingPlan
   sellingPlanGroups(first: 10) { nodes { appName name sellingPlans(first: 20) { nodes { id name recurringDeliveries } } } }
   and on each variant:
   sellingPlanAllocations(first: 20) { nodes { sellingPlan { id name } priceAdjustments { price { amount currencyCode } compareAtPrice { amount currencyCode } } } }
   If the site uses a private Storefront access token, keep it on the server.2. Show the plan's own price. For a subscription or membership option, show priceAdjustments[0].price from the selected variant's sellingPlanAllocations for that plan. That is the price Shopify charges at checkout. If a second price adjustment is returned, show it as the price from later orders. Where the page has a price written in for a subscription or a membership tier, replace it with the value from the API. Offer a subscribe option only on a product that returns a selling plan; a product that returns none is sold as a one-time purchase only.3. Membership tiers. Each tier's Join button maps to a variant of the membership product and one of the selling plans allocated to that variant (match by the variant and plan names the API returns). Where a variant has more than one plan (for example 6 months and 12 months), show one Join option per plan. The button adds that variant with that selling plan. A membership product returns requiresSellingPlan true, so it is never added without one.4. Put the plan on the cart line. In cartCreate and cartLinesAdd, send each subscription line as { merchandiseId: <variant id>, quantity: 1, sellingPlanId: <selling plan id> }, with both ids exactly as the API returned them (gid://shopify/ProductVariant/... and gid://shopify/SellingPlan/...). A one-time line has no sellingPlanId. In the response, ask for cart { id checkoutUrl lines(first: 20) { nodes { id quantity sellingPlanAllocation { sellingPlan { id name } } } } }, userErrors { field message } and warnings { code message }. If userErrors is not empty, show the message and do not open checkout. Never turn a subscription line into a one-time line on your own. Show each line's plan name in the cart, then send the shopper to cart.checkoutUrl.5. A way back in. Add a "Manage my subscription" link to the footer and to the account or member area, pointing to the PORTAL LINK above. Subscribers skip, pause and cancel there. Do not build those actions on this site.6. Test before any button goes live. In a preview, for every tier and every subscribe option: add it to the cart, check that the cart response has sellingPlanAllocation set on that line, open checkoutUrl and check that the checkout shows the item as a subscription with the right price and delivery frequency. Do not place an order. Tell me which checks passed and which failed, and change nothing else on the site.

Check it before you go live

  • In the preview, add each subscription option and each membership tier to the cart. Each line should show its plan name.

  • Open checkout. The item should show as a subscription, with the plan's price and delivery frequency. You do not need to place an order.

  • If a membership is added without its plan, Shopify refuses it with the message "Variant can only be purchased with a selling plan." The site should show that message and stay on the page.

What the prompt does and does not do

  • The prompt covers subscribe-and-save plans and memberships. Build-a-box, fixed bundles, volume discounts and the Subi cart drawer still need a Liquid theme, so they do not work on a Lovable or Replit site.

  • The Subi widget for headless storefronts shows plans on a single product page, but not membership tiers, build-a-box, fixed bundles or volume discounts. See How do I add the Subi subscription widget to a Hydrogen or headless storefront?. That is why the prompt has your site show its own Join buttons for membership tiers.

  • Your subscribers manage their subscriptions in the Subi customer portal, through the link the prompt adds. In the portal, they can skip, pause, resume and cancel by default.

  • The steps above use Shopify's Storefront API and checkout only. They need no Subi API key.

Members' access and your own backend

If your membership gives members something on your site or elsewhere, such as a members' area, a Discord role or a course, your site's backend can follow each membership through Subi's webhooks. The API and webhooks are included in every current paid Subi plan and in most older plans, most older Free and Essential plans among them. If your plan does not include them, the Webhooks and API keys pages in Subi show an Upgrade your plan button. The Free plan offered to new stores today does not include them.

Under Settings > Webhooks in Subi, if the page reads Inactive, open More actions at the top of the page and click Turn on webhooks, then create one webhook with your function's URL for each of Subscription Created, Subscription Resumed, Subscription Paused and Subscription Canceled. The same page shows the secret that signs every delivery. Make an API key under Settings > API keys. Then paste this into the builder:

Add a server function that receives Subi webhooks at /api/subi-webhook (a Supabase Edge Function in Lovable, a server route in Replit).1. Read the raw request body before parsing any JSON. Compute HMAC-SHA256 of the raw bytes with SUBI_WEBHOOK_SECRET, encode the result as base64, and compare it with the X-Subi-Hmac-Sha256 header in constant time. If they differ, answer 401 and stop.
2. Answer 200 within 10 seconds and do any slow work after answering. Do not redirect: a 3xx answer counts as a failed delivery.
3. Keep SUBI_WEBHOOK_SECRET and SUBI_API_KEY as server secrets. Never send either to the browser.
4. Webhooks name the customer by their Shopify customer ID (subscription_contract.customer_id). For the email, call GET https://api.subi.co/public/v1.0/subscribers/<customer_id>/ from this server function with the key in the X-Api-Key header.
5. Read the event name from the X-Subi-Topic header. On SUBSCRIPTION_CREATED and SUBSCRIPTION_RESUMED, give access only if one of subscription_contract.lines has the membership product's product_id. On SUBSCRIPTION_PAUSED and SUBSCRIPTION_CANCELED, take access away unless the customer still has another active contract with that product: list GET https://api.subi.co/public/v1.0/subscription-contracts/?customer_id=<customer_id>&status=ACTIVE and read each one with GET https://api.subi.co/public/v1.0/subscription-contracts/<id>/, the only call that returns its lines.
6. Use the X-Subi-Event-Id header so the same event is handled only once.
7. Subi sends no login token, so this endpoint must accept requests without one (in a Supabase Edge Function, turn off JWT verification for it). Tell me the full https URL to register in Subi.

A membership that ends after its last payment, such as a prepaid one, sends no webhook when it ends. The members' access article below describes the nightly check that catches it.

See Keep your members' Discord, Telegram, course or members' area access in step with their Subi membership for when access is given and taken away, How to set up and manage Webhooks in Subi for the webhook events and the test you can send, and the Subi API guide for the calls.

Did this answer your question?