Case study
A Shopify app that put a licensed Marvel catalog into any merchant’s store
Merchants install the app, import Marvel products, and receive new ones automatically from the licensee’s platform. Three companies, one integration, a fixed launch date.
The constraint
Mad Engine holds a license to make Marvel apparel and merchandise. They wanted any Shopify merchant to be able to install an app, pull the Marvel catalog into their own store, sell it, and have orders fulfilled by Mad Engine without touching a spreadsheet. New products had to appear in every subscribed store automatically. Sunrise Integration was hired to build it, and I led the team that did.
Three things made it harder than a normal Shopify app:
- Three organizations had to agree on one contract. Sunrise built the app. Mad Engine’s own developers owned the fulfillment system’s API, and their DevOps owned the environments it ran in. Shopify owned the platform, its APIs, and its rules. Every field in the mapping between the fulfillment system and Shopify had a person on each side who had to agree it meant the same thing. The product sync, for example, could not start until the field mapping was written down as a shared interface that both teams built against. Product identifiers were the main sticking point: the fulfillment system and Shopify each had their own, and the mapping between them had to be settled before anything else could be built.
- Volume came in bursts, and Shopify meters every call. One catalog, many stores. A new product or a price change fans out to every subscribed shop, and each shop has its own Shopify API rate limit. There were more than thirty shops at launch. Order events came back the other way, and Shopify retries webhooks it thinks failed, so the same order could arrive more than once.
- The date was fixed. Launch was set for June 2022. There was no option to slip it and add capacity later.
- It was licensed IP. The app could only distribute imagery, products, and descriptions that had been approved for the license, so the catalog itself was a compliance boundary, not just a data source.
Options considered
- Synchronous integration. Shopify webhook arrives, the app calls the fulfillment API, done. Simplest to build and reason about. Fails the moment either side is slow or down: Shopify’s webhook times out, retries, and now the same order is in flight twice. No way to throttle fan-out to dozens of stores without blocking.
- Scheduled batch sync. Poll the catalog and orders on a timer, reconcile differences. Robust to outages and easy to rate-limit. But “new products appear automatically” becomes “new products appear within N minutes,” and a missed or partial run is hard to detect and harder to explain to a merchant whose order sat unfulfilled.
- Event-driven with queues. Every inbound event, whether a Shopify webhook or a catalog change from Mad Engine, is written to a queue first and acknowledged immediately. Workers consume at a rate each downstream can take, retry with backoff, and park what still fails in a dead-letter queue for a human. Costs: eventual consistency that has to be explained to the client, idempotency that has to be designed rather than assumed, and more moving parts to observe.
What was chosen and why
Option 3, on AWS: Lambda workers fed by SQS queues, with the Express app handling installs, the merchant UI, and webhook intake. That choice was the architect’s. My job was to make it hold across three companies and ship it on the date.
The tradeoff accepted: the client bought reliability under burst load in exchange for a system where “did my order go through” is answered by a queue’s state, not a synchronous response. That is harder to demo and harder to support, so part of the work was giving Mad Engine’s team the visibility to answer that question themselves.
My part was the seams. The architecture was decided. What was not decided was every field, status, and failure mode at the boundary between Mad Engine’s fulfillment system and the app, and that boundary belonged to nobody until I took it. I ran the mapping with their developers, got environment and credential questions answered by their DevOps, and turned the result into a contract both teams built against. On our side I split the work across the developers, kept the integration tests as the definition of done, and ran the stress tests before launch.
Design details:
- Idempotency: duplicate webhooks were caught by checking the entity ID against what had already been processed before any work was done. Without that, queues make duplication worse, not better.
- Rate limiting per store: throttling was per shop on our side, matching Shopify’s per-shop limit. A global throttle would have been either too slow for small fan-outs or too fast for one busy store.
- Fan-out for catalog changes: one message per subscribed store, so a change to one product reached every store and a slow store never delayed the others.
- Stress testing: we simulated order bursts, shop subscriptions, and catalog sync. The first catalog sync run failed under load, and we reworked the asynchronous flow before launch.
What happened
- Delivered the app and the infrastructure on the launch date.
- At launch the system behaved as it had under stress testing. A few edge cases surfaced in the first days and were caught quickly, because logging was structured and the observability platform was already in place.
- The app began collecting positive reviews from merchants on the Shopify App Store.
If I did it again
I would have pushed to settle the integration contract before any code was written, instead of in parallel with it. I would have instrumented the queues for the client’s support team from day one rather than after they asked. And I would have chosen a different language for the heavy order-processing workers.
Open to technical lead and solutions architect roles, remote. Get in touch or read the other case studies.
