implementation 2026-04-18 17 min read the implementation desk

Implementation guide: Shopify headless multi-brand checkout on one parent account

3-minute scan
  • Shopify headless with a parent merchant account means the Storefront API powers your custom frontend while the parent gateway owns the checkout — you skip Shopify Payments entirely.
  • The load-bearing decision is where the cart lives: Shopify cart (draft orders) vs. your own cart service. For multi-brand, your own cart service wins because one cart can hold SKUs from multiple Shopify stores.
  • Expect 60–80 engineering hours for the first brand, 10–15 for each additional brand. The investment pays back inside 120 days on portfolios above $500k/month.
On this page

    Shopify locks merchants into Shopify Payments with an explicit tax — use any other gateway and Shopify charges a 0.5–2.0% extra transaction fee on top of your processor's rate. For a single brand this is annoying. For a multi-brand portfolio it is a five- to six-figure annual penalty. The escape hatch is headless: strip Shopify down to a product and inventory API, run the cart and checkout on your own stack, and settle payments through a parent merchant account that neither knows nor cares that Shopify exists. This guide walks the exact wiring.

    1. Why headless for multi-brand specifically

    Hosted Shopify tries to make each store an island — its own checkout, its own Shopify Payments account, its own analytics. For portfolio operators this is the opposite of what you want. Headless flips the relationship: each Shopify store becomes a product database, and your custom frontend composes cross-brand carts, shared loyalty, shared upsell logic, and a single checkout that settles through the parent.

    The 0.5–2.0% transaction-fee penalty disappears because you never invoke Shopify Payments and you never touch the Shopify checkout. Legally, this is inside Shopify's ToS as long as you use the Storefront API (not Checkout API) and do not call your thing a Shopify Checkout extension.

    2. Architecture at a glance

    The stack has four layers:

    1. Shopify stores (one per brand): product catalog, inventory, order writeback.
    2. Frontend (Next.js / Hydrogen / Remix): renders product pages, cart, checkout UI. Reads from Storefront API. Writes orders to Shopify via Admin API after payment success.
    3. Cart service (your own, Redis-backed): session-scoped cart holding items from any brand.
    4. Parent gateway: tokenizes card, runs 3DS, charges with per-brand descriptor, fires webhook to your order-writeback service.

    3. Storefront API wiring

    Each brand's Shopify admin generates a Storefront API access token with unauthenticated_read_product_listings, unauthenticated_read_product_inventory, and unauthenticated_write_checkouts scopes (the last one is a legacy permission — you only need it if you later switch to Shopify Checkout). Store tokens in your backend, never in the client bundle.

    // lib/shopify.ts
    const SHOPIFY_STORES = {
      brand_a: { domain: 'brand-a.myshopify.com', token: process.env.BRAND_A_STOREFRONT_TOKEN },
      brand_b: { domain: 'brand-b.myshopify.com', token: process.env.BRAND_B_STOREFRONT_TOKEN },
      brand_c: { domain: 'brand-c.myshopify.com', token: process.env.BRAND_C_STOREFRONT_TOKEN },
    };
    
    export async function fetchProduct(brand, handle) {
      const { domain, token } = SHOPIFY_STORES[brand];
      const res = await fetch(`https://${domain}/api/2024-01/graphql.json`, {
        method: 'POST',
        headers: { 'X-Shopify-Storefront-Access-Token': token, 'Content-Type': 'application/json' },
        body: JSON.stringify({ query: PRODUCT_QUERY, variables: { handle } }),
      });
      return res.json();
    }

    4. Cross-brand cart service

    Store the cart in Redis keyed by a signed session cookie. Each line item carries brand, variant_id, quantity, price, image, and sku. When the user checks out, your checkout endpoint sums the cart, fetches the live price from each Shopify store's Storefront API (defensive re-pricing), calculates tax and shipping, and hands the total to the parent gateway.

    // POST /api/checkout/init
    const cart = await redis.get(`cart:${sessionId}`);
    const lines = JSON.parse(cart);
    const total = await recomputeTotal(lines);  // hits Storefront API per brand
    const intent = await parentGateway.createPaymentIntent({
      amount: total,
      currency: 'USD',
      metadata: { session_id: sessionId, brands: lines.map(l => l.brand) },
      statement_descriptor_suffix: lines.length === 1 ? BRAND_DESCRIPTORS[lines[0].brand] : 'MULTIBRAND',
    });
    return { client_secret: intent.client_secret };

    The statement_descriptor_suffix decision is non-trivial: if the cart is single-brand, use that brand's descriptor. If the cart is multi-brand, use a portfolio descriptor like MF*MULTI with the line-item breakdown in the customer's emailed receipt. Chargeback risk rises with generic descriptors, so keep receipts crisp.

    5. Payment element integration

    On the frontend checkout page, mount the parent gateway's payment element (Stripe Elements, Adyen Components, or whatever the parent provides) with the client_secret from step 4. Confirm the payment client-side, then on success, POST to /api/checkout/complete with the payment intent ID.

    6. Order writeback

    The /api/checkout/complete endpoint does three things: verifies the payment intent succeeded via the parent gateway API, writes one order to each affected Shopify store via the Admin API (using orderCreate mutation with financial_status: PAID), and fires the order-confirmation email. This is the most error-prone step — idempotency matters enormously here. Use the payment intent ID as the idempotency key so double-submits do not create duplicate orders.

    // POST /api/checkout/complete
    const intent = await parentGateway.retrievePaymentIntent(piId);
    if (intent.status !== 'succeeded') throw new Error('Payment not complete');
    
    const existing = await db.orders.findOne({ payment_intent_id: piId });
    if (existing) return existing;  // idempotency
    
    const orders = [];
    for (const brandLines of groupByBrand(intent.metadata.lines)) {
      const order = await shopifyAdmin[brandLines.brand].orderCreate({
        line_items: brandLines.items,
        financial_status: 'PAID',
        transactions: [{ kind: 'sale', gateway: 'manual', amount: brandLines.subtotal }],
      });
      orders.push(order);
    }
    await db.orders.insert({ payment_intent_id: piId, shopify_orders: orders });
    return orders;

    7. Refund routing

    Refunds are the hardest part of multi-brand headless. A customer emails you asking for a refund on their multi-brand cart — which brand gets the refund? The pattern: refunds are issued per-line-item, not per-payment-intent. Your admin tool lets the operator select the lines to refund, computes the refund amount, calls parentGateway.refund(piId, amount), and also calls the corresponding Shopify store's orderRefund mutation so Shopify inventory and analytics stay consistent.

    See refund routing across sub-brands for the full refund flow.

    8. Inventory sync under contention

    Two customers buy the last unit of brand_a/sku_X at the same moment. Who wins? The answer depends on when you reserve inventory. Reserve at cart-add (safe but causes abandonment to lock stock) or reserve at payment-intent creation (better UX but requires compensating rollback if payment fails). We recommend the second pattern with a 15-minute TTL on the reservation.

    9. Apple Pay on headless

    Each brand's frontend domain needs an Apple Pay domain association file served from /.well-known/apple-developer-merchantid-domain-association. In Next.js, place the file in public/.well-known/ and Next will serve it. Trigger verification from the parent gateway dashboard per domain.

    10. Gotchas

    • Shopify order count limits: Admin API caps orderCreate at 40 per minute per store. If you run a Black Friday spike, queue writebacks and acknowledge payment separately from inventory decrement.
    • Tax calculation: Do not rely on Shopify's tax engine for the checkout total — it runs inside Shopify checkout, which you are not using. Use Avalara, TaxJar, or Stripe Tax called from your checkout service.
    • Shopify discount codes: Discount codes configured in Shopify admin do not apply automatically to your custom checkout. Rebuild discount logic in your cart service or duplicate the discount model.
    • Webhooks: Shopify webhooks (orders/create, orders/paid) still fire when you orderCreate via Admin API. If downstream apps (Klaviyo, fulfillment) listen to those, you get the integrations for free.
    • SEO: Shopify product pages at brand-a.myshopify.com/products/* stay indexable unless you canonical them to your custom frontend. Always set the canonical URL in product metafields.

    11. Cost and ROI

    For a 6-brand portfolio doing $1.2M/month total, headless migration costs roughly $30k in engineering and takes 8 weeks. Annual savings from dropping Shopify Payments' extra-gateway tax: $72k–$288k depending on the per-transaction penalty tier. Plus you gain full control over checkout UX, dispute evidence (not locked behind Shopify), and analytics. Payback is 90–150 days.

    Ready to architect your headless multi-brand checkout? Submit the 12-question intake and we will return a build plan within 72 hours. Or review how the parent routing actually works before committing.

    Found this useful? Share it X LinkedIn Reddit HN Email

    FAQ

    Does this work with Hydrogen or only Next.js?
    Both. Hydrogen ships with the Storefront API client pre-wired, which saves about 10 hours of setup. Everything else — cart service, parent gateway integration, order writeback — is identical.
    Will Shopify kick us off for using a non-Shopify gateway?
    No, as long as you stay on the Storefront API and do not embed Shopify Checkout. Shopify's ToS prohibits bypassing Shopify Payments only when you are using Shopify Checkout. Headless storefronts with their own checkout are explicitly supported — Hydrogen itself is built for this pattern.
    Do we lose Shopify Shop Pay?
    Yes — Shop Pay only works on Shopify Checkout. You gain Apple Pay and Google Pay through the parent gateway, which covers most of the Shop Pay conversion lift.
    Can we keep some brands on hosted Shopify and only go headless for others?
    Yes, and it's the most common starting point. Pilot headless on one brand, prove the economics, then roll out to the rest over 6–12 months.
    How do we handle subscriptions?
    Shopify's Selling Plans API works headlessly, but billing runs through your parent gateway via a scheduled job, not Shopify Subscriptions. Plan 20–30 hours to wire the recurring-billing service.

    Running multiple brands?
    multiflow was built for this.

    The Operator Briefing

    Twice-monthly. No fluff.

    Processor shutdowns, reserve-hold playbooks, reconciliation lessons, and the merchant-account decisions that save operators six-figure years. Delivered to your inbox — never spam.

    No spam. Unsubscribe in one click.

    We use essential cookies · Privacy