App Development

Building Modular Shopify Apps with Remix: Multi-marketplace sync dashboard

Integrating Shopify inventory with custom sales channels like Temu, Allegro, or Onbuy is best handled directly in the admin dashboard. Here is how we decoupled our core marketplace synchronization engine.

By Rehan Idrisi · · Updated · 5 min read

Part of: Shopify

Merchants prefer to run their business from a single dashboard. Forcing them to log in to external platforms to configure inventory mapping, product listings or shipping tracking is a major UX friction point.

To solve that for our clients, we extracted the key synchronization processes out of our core synchronization engine and built modular, embedded Shopify apps for Onbuy, Temu and Allegro using Shopify's Remix framework. Here is how the integration is architected.

One-time SKU mapping

Mapping is the foundation of multi-channel synchronization. We built an intuitive SKU mapper directly inside the Shopify admin panel, using Polaris components.

Once a merchant maps a Shopify SKU to its marketplace counterpart, the app saves that pair to our central database, and bidirectional updates start flowing automatically.

Allow mapping configuration inside their native dashboard to minimize friction and prevent sync errors.

Bidirectional order and inventory sync

A customer buys on Temu, Onbuy or Allegro. Our webhook receiver intercepts the transaction and creates a corresponding order in the merchant's Shopify admin automatically.

The merchant fulfils that order and generates shipping labels through their existing Shopify app flow, and our background queue immediately relays the tracking details back to the source marketplace.

Embedding the UI with Remix and Shopify App Bridge

Remix loaders and actions let us fetch mapped products in the backend and render them instantly on the frontend, inside the Shopify iframe. Merchants never feel like they left Shopify.

import { json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';

export const loader = async ({ request }) => {
  const { admin } = await authenticate.admin(request);
  // Fetch mapped products from internal sync database
  const mappings = await db.getMappings(admin.shop);
  return json({ mappings });
};

export default function Dashboard() {
  const { mappings } = useLoaderData();
  return (
    <Page title="Marketplace Mapping">
      <Layout>
        <Card sectioned>
          <p>You have {mappings.length} active product mappings.</p>
        </Card>
      </Layout>
    </Page>
  );
}

Managing Shopify rate limits

Shopify's API limits work on a leaky bucket algorithm. Push millions of SKU updates at once during a peak sale and you trigger HTTP 429 rate limit exceptions, which blocks sync entirely.

Every update is queued in BullMQ instead. The queue processors calculate the remaining API budget from Shopify's headers and wait before firing new requests, which keeps transfers smooth and uninterrupted.

Verifying a webhook really came from Shopify

Every webhook Shopify sends includes an X-Shopify-Hmac-SHA256 header, a signature computed from the request body and the app's shared secret. We recompute that signature ourselves before touching the payload, and reject anything that doesn't match. Skip this step and the order-creation endpoint is a public URL that will accept and act on whatever gets POSTed to it. It is a small piece of middleware. It sits in front of every webhook route, not just the marketplace-order one.

Retries mean idempotent processing, not just fast responses

Shopify expects a webhook endpoint to respond quickly, and will retry delivery if it doesn't hear back in time or gets a non-2xx response. The same webhook can therefore arrive more than once for the same event. We key incoming order webhooks by Shopify's own order ID and check for an existing sync record before creating anything, so a retried delivery updates the existing record instead of creating a duplicate order on the marketplace side. Acknowledging the webhook immediately and doing the actual work in the BullMQ queue afterwards keeps the response fast enough that retries stay rare in the first place.

Keeping each merchant's data in its own lane

Many merchants install the app independently, so nothing about the data model can assume there is only one shop. Every mapping, queue job and webhook handler carries the shop's domain as a first-class key, and offline access tokens are stored per shop instead of in a single shared credential. A bug that leaks one merchant's product mappings into another's dashboard is obvious in hindsight and very easy to introduce by accident. Shop scoping has to be enforced at the data layer from the start. Bolting it on later never covers everything.

Testing sync logic without a live marketplace order

Waiting for a real order on Temu, Onbuy or Allegro isn't a workflow for testing a code change. We replay captured webhook payloads, anonymised and taken from real historical events, against a local instance to exercise the order-creation and inventory-update paths. Outbound calls go to each marketplace's sandbox or test-store mode, so a bad deploy can't create a real listing or charge a real customer. The Remix loader and action logic is plain server-side code, and tests the same way as any other backend function, independent of the Shopify iframe it eventually renders inside.

When a marketplace API is simply down

Rate limiting is the routine case. An outright outage on the marketplace side is the one that needs a different response. BullMQ's retry-with-backoff handles transient failures automatically, but a sustained outage just means the queue backs up while retries keep failing. We surface sync status per product and per channel directly in the merchant's Shopify admin, so a merchant sees a pending-sync state instead of a silent failure, and we cap the retry backoff so a recovered marketplace drains the backlog in a bounded window instead of retrying at an ever-lengthening interval.

Scaling and rate limit practices

  • Queue Shopify API calls using Redis, so merchant API rate limits are strictly respected.
  • Buffer high-volume inventory modifications, which is what prevents concurrency conflicts during store-wide updates.
  • Listen to Shopify's product update webhooks to trigger real-time stock sync on external channels.

Embed the synchronization features directly into the tools your merchants already use everyday. Good integrations should be invisible.

How do you sync Shopify inventory with marketplaces without hitting rate limits?

Queue every API update through Redis and BullMQ instead of firing calls directly, and calculate the remaining request budget from Shopify's rate-limit response headers before each batch. Paired with a webhook receiver for inbound marketplace orders, that keeps bidirectional sync inside Shopify's leaky-bucket limits even during peak sales.

Share this guideLinkedInXWhatsAppFacebook
All guides