Skip to Content
Your subscription data now goes where you need it: the Curobi Developer API feeds your warehouse, ERP or CRM, and webhooks tell you the moment something changes — read the API docs →
Developer APIHeadless & Custom Storefronts

Headless & custom storefronts

This page is the integration contract for a storefront that isn’t a Liquid theme — Hydrogen, a custom React front end, a native app, or anything else talking to Shopify’s Storefront API. It says what your cart must send, and what happens when it sends it wrong.

A headless storefront can create Curobi subscriptions today

With no code from Curobi, and no Curobi credential.

Subscriptions on Shopify are selling plans. A cart line carrying a sellingPlanId produces a checkout that Shopify turns into a SubscriptionContract, which fires orders/create, which Curobi mirrors on exactly the same path a Liquid theme uses. Nothing in that webhook knows or cares whether a theme app extension was involved.

Everything below is about the extras — build-a-box, Mix & Match, gifts. Those ride on cart-line properties, and they’re the part that fails silently.

Get a property key wrong and nothing errors. The add-to-cart succeeds, checkout succeeds, the subscription is created, the order ships — and the feature simply never happens. No box selection is recorded. No gift is captured. No discount group is formed.

There is no runtime validation that can catch this for you: by the time the order reaches us, a wrong key is indistinguishable from the merchant not having used the feature. Copy the keys below exactly, then place a real test order and confirm it appears in the Curobi admin.

Cart-line properties

Written at add-to-cart, carried through checkout onto the order line’s customAttributes, and read there by Curobi.

KeyFeatureValueVisible to buyer
_rc_box_pickBuild-a-boxvariantGid=qty segments, comma-joinedno
Your picksBuild-a-boxHuman-readable companion, e.g. Coffee ×2, Teayes
_rc_mm_groupMix & MatchThe plan’s cart_group_idno
_rc_giftGiftAny truthy value — 1, true, yesno
_rc_gift_emailGiftRecipient email — requiredno
_rc_gift_startGiftOptional YYYY-MM-DD first deliveryno
Gift recipientGiftRecipient nameyes
Gift messageGiftThe gifter’s noteyes

The leading _ is Shopify’s convention for hiding a property from the buyer-facing order summary. The unprefixed keys are visible on purpose: who the gift is for, the gift note and the box contents are exactly what a merchant wants on the order and the packing slip. Preserve that split — it’s part of the contract, not an accident of naming. A headless build that hides all eight produces orders the fulfilment team can’t read.

These keys are add-only. Renaming one would orphan in-flight carts, so they won’t change.

Build-a-box

_rc_box_pick carries the picks:

gid://shopify/ProductVariant/44000000000001=2,gid://shopify/ProductVariant/44000000000002=1
  • Segments joined by ,; variant and quantity separated by =. Neither character appears in a variant GID, so the split is unambiguous.
  • A bare GID with no =qty is treated as quantity 1 — a legacy format, still accepted.
  • Quantity is clamped server-side to 1–10 per item, and a repeated variant is summed then re-clamped.
  • Use the GID, not the numeric id. The menu metafield gives you GIDs; pass them through unchanged.

The server re-validates every pick against the published menu and the plan’s min/max bounds, so a crafted payload can’t smuggle in an item that isn’t on the menu. Invalid picks are dropped, not rejected — another reason to test with a real order.

Mix & Match

_rc_mm_group is what groups the lines of one selection.

  • Every line of one selection must carry the same value.
  • The value is the plan’s cart_group_id — available from the mix_match product metafield as groupId, or from GET /api/admin/v1/mix-match/{id} as cart_group_id.
  • Set selling_plan on each line only when the shopper chose to subscribe. Omit it for one-time purchases.
  • Don’t put the group id on lines that aren’t part of the selection.

The tier discount is applied by a Shopify Product Discount Function that runs server-side at checkout and re-derives the tier from real cart quantities. So the discount lands whether the picker was Liquid, React or curl, and a crafted cart can’t claim a richer tier than it bought.

Gifts

_rc_gift_email is mandatory. A gift line without a plausible recipient email is rejected outright rather than stored as a half-gift — there would be nobody to send the claim link to.

Bounds enforced server-side: recipient name ≤ 100 characters, message ≤ 500 characters. _rc_gift_start must be YYYY-MM-DD and a real calendar date.

A gift is only captured if the selling plan itself is marked giftable by the merchant. A cart carrying the gift properties against a non-giftable plan produces an ordinary subscription, silently and by design — check giftable on GET /plans.

Configuration you can already read

Every Curobi config metafield is declared access.storefront = "public_read", so the Shopify Storefront API returns them to a headless front end today, with no Curobi credential involved.

MetafieldContains
product.metafields.app.bundle_planThe bundle’s selling-plan id
product.metafields.app.bundle_contentsThis period’s locked curated box
product.metafields.app.bundle_menuThis period’s build-a-box menu — variant GIDs, titles, images, prices
product.metafields.app.mix_matchTier config: groupId, sellingPlanId, mode, tiers, min, max
shop.metafields.app.gift_plansThe shop’s giftable selling-plan ids

A curobi namespace exists as a legacy fallback for each; read app first and fall back only if you’re supporting an older install.

So a headless build already has, with zero integration work: which products are subscribable, on what plans, at what cadence and discount, what’s in this period’s box, what the Mix & Match tiers are, and which plans accept gifts.

What still needs the Admin API

Three gaps, and the honest workaround for each.

The Mix & Match pickable catalog. Served through the Shopify App Proxy today, which a headless front end can’t call — there’s no /apps/curobi path, and cross-origin to .myshopify.com fails CORS. It’s proxied rather than inlined because a large catalog exceeds the metafield size ceiling, so another metafield can’t fix it. Read it from GET /mix-match/{id} server-side for now.

The gift disclosure. Same shape, and it matters more than its size suggests: the disclosure exists so a shopper ticking “This is a gift” learns before add-to-cart whether the buyer keeps being charged. If you can’t fetch it, render the conservative wording — that a recurring gift continues billing the buyer until cancelled. Every failure path in our own implementation falls back that way. Rendering nothing on a fetch failure is the one behaviour to avoid.

The customer portal. Curobi’s portal renders inside Shopify-hosted customer accounts, so there is no way to embed subscription management in your own front end today. Send subscribers to their Shopify customer account page instead — a headless store already using the Customer Account API for login can link them straight there, and the portal works normally once they arrive. Don’t build a bespoke management UI against the portal in the meantime: there is no public endpoint behind it for you to call.

Two limits that will not be lifted

Contracts created by another app are invisible. Curobi holds Shopify’s read_own_subscription_contracts / write_own_subscription_contracts scopes. It can’t read, manage or report on a subscription another app created, even on a store where Curobi is installed. Migration is via CSV import, which creates real Curobi-owned contracts. There is no read-only overlay mode, and this is a Shopify scope boundary rather than a Curobi decision.

Gift recipients can never have a login. Shopify has no third-party-payer concept — the contract belongs to whoever holds the card, and a contract can’t be transferred between customers. Recipients get a tokenized claim page instead of portal access — and you don’t have to build it. Curobi serves it through the Shopify App Proxy, so the emailed link reaches the recipient whatever your storefront is written in.

The catch for a headless store: it renders as Liquid inside the shop’s online-store theme, not your front end. That needs a published theme to exist, and it will inherit however that theme looks. Keep it presentable — to a recipient who has never heard of Curobi, a page in the merchant’s own branding on the merchant’s own domain is the difference between a gift and a phishing email.

Capability matrix

CapabilityHeadless todayWith the Admin API
Read plans, cadence, discounts✅ metafields
Create a subscription at checkoutsellingPlanId
Curated box contents / build-a-box menu✅ metafields
Build-a-box pick capture✅ cart-line property
Mix & Match tier config✅ metafield
Mix & Match pickable catalog✅ server-side
Mix & Match live price preview⚠️ port the math, or none
Mix & Match checkout discount✅ function runs server-side
Gift capture at checkout✅ cart-line properties
Gift disclosure before add-to-cart❌ use conservative copy✅ server-side
Customer portal❌ link to Shopify customer accounts
Gift recipient claim page✅ App Proxy, needs a published theme

Checklist before you ship

Cart lines carry sellingPlanId for subscriptions, and omit it for one-time purchases

Build-a-box lines carry _rc_box_pick with GIDs, plus a readable Your picks

Every line of one Mix & Match selection carries the same _rc_mm_group

Gift lines carry _rc_gift and _rc_gift_email, plus the two visible gift properties

The gift disclosure renders conservative copy when it can’t be fetched

You placed a real test order

And confirmed the box selection, gift or bundle actually appears in the Curobi admin. Nothing above throws when it’s wrong, so this step is the test.

Last updated on