Scaling a Shopify App: Webhooks, Queues and the Five-Second Rule
Alexander Mercer
Sep 1, 2026
A Shopify app that works beautifully for fifty stores can fall over at five hundred without a single line of code changing. Nothing was wrong with the code. What changed is that the assumptions it was built on — that API calls always succeed, that webhooks arrive at a manageable rate, that a sync can happen inline — stop being true somewhere between those two numbers.
The failure is rarely dramatic. It looks like a support queue slowly filling with merchants reporting that their inventory is a few hours stale, and an engineering team that cannot reproduce it because on their test store everything is instant.
Rate limits are a budget, not an error condition
Shopify's Admin GraphQL API uses a leaky-bucket cost model: every query has a calculated cost, you hold a bucket of points, and the bucket refills at a fixed rate. The REST API works on a similar principle with a request bucket. Either way, the limit is per shop, not per app — which is the detail that changes how you should think about it.
Because it is per shop, adding merchants does not shrink each merchant's budget. What it does is multiply the number of independent budgets your infrastructure has to track. A single global rate limiter across your whole app is the wrong shape: it throttles shop A because shop B is busy, which is a self-inflicted outage.
Read the cost extensions Shopify returns on every GraphQL response. They tell you your current bucket state exactly. An app that reads them and paces itself will outperform one that fires requests until it gets a 429 and then backs off, because by the time you see the 429 you have already lost the round trip.
Never do real work inside a webhook handler
Shopify gives a webhook handler five seconds to respond. Not five seconds to finish the job — five seconds to acknowledge receipt. Exceed it enough times and Shopify stops delivering to that endpoint entirely, which is a silent failure mode: your app does not error, it simply stops hearing about orders.
The correct handler is almost boring. Verify the HMAC, write the payload to a queue, return 200. Everything else happens somewhere else, where it can take as long as it needs, retry on failure, and be observed.
This also solves a problem teams often try to solve separately. During a flash sale, a single store can emit orders/create faster than any synchronous handler could process them. With a queue in front, that burst becomes a depth measurement on a graph rather than an incident.
The queue is where the real design happens
Once work is queued, the questions that matter are ordering, isolation and poison handling. Ordering, because processing an inventory update out of sequence produces a number that is wrong in a way that self-heals only if another update happens to arrive. Isolation, because one merchant's backlog must not starve everyone else's. Poison handling, because one malformed payload should not stall a partition forever.
Partitioning by shop domain handles the first two at once: ordering is preserved within a shop, which is the only place it matters, and a slow shop occupies its own lane. For the third, a dead-letter queue with an alert is the whole answer, and the alert is the important half — a dead-letter queue nobody looks at is a data-loss mechanism with extra steps.
Retries need jitter. Synchronised retries after a transient Shopify outage produce a thundering herd precisely when the platform is least able to absorb it.
“An app that reads its rate-limit budget and paces itself will beat one that fires until it gets a 429 — by the time you see the error, you have already lost the round trip.”
HelloDevs
Use bulk operations for anything that touches a whole catalogue
Paginating a hundred thousand products through the standard API is the wrong tool, and it is the most common cause of an app that takes six hours to onboard a large merchant. Shopify's bulk operations API exists for exactly this: you submit a query, Shopify runs it asynchronously, and you download a JSONL file when it is ready.
The trade-off is that bulk operations are asynchronous and there is a limit on concurrent operations per shop, so your onboarding flow has to be built around polling and resumability rather than a progress bar that assumes linear progress. That is more work up front and dramatically less work later.
The same applies in reverse for writes. bulkOperationRunMutation exists, and hand-rolling ten thousand sequential mutations instead is how apps end up permanently in rate-limit backoff.
What to instrument before you need it
Queue depth, per partition
Aggregate depth hides the failure that matters. One shop's lane at fifty thousand and everyone else at zero is a very different problem from uniform load.
Rate-limit headroom, per shop
Track the bucket state Shopify already reports. Falling headroom is a leading indicator; a 429 is a lagging one.
Webhook acknowledgement latency
The p99 matters more than the mean. You are five seconds from being unsubscribed and you will not be told.
Dead-letter arrivals
Alert on the first one, not on a threshold. The first poison message is information; the thousandth is an outage.
End-to-end sync lag
Time from Shopify's event timestamp to your data being correct. It is the only number a merchant actually experiences.
Build for the second thousand stores
None of this is exotic infrastructure. It is a queue, a per-tenant rate limiter, a dead-letter path and five dashboards. What makes it hard is that every one of them is unnecessary at ten stores, and retrofitting them at a thousand means doing it while the support queue is on fire.
The honest middle ground is to build the seams early and the sophistication late. Put the queue in from day one even if it has one consumer and no partitioning, because the expensive part is not the queue — it is untangling a codebase that assumed synchronous execution everywhere.
We build this shape of system in our Shopify app development and SaaS product development work. If you have an app that is starting to feel its scale, start a conversation.
Alexander Mercer
Senior Tech Writer at HelloDevs
Alexander covers software engineering culture, design systems, and frontend developer experience. When not writing, you can find him debugging React or exploring typography design.
Stay Updated
Get raw, honest dev-first tutorials and tech blog articles delivered straight to your inbox once a week.
Our guide
- A Shopify development agency builds and maintains the parts of a store Shopify does not give you out of the box. That means custom themes, apps, integrations with your other systems, and ongoing fixes. HelloDevs does all of this and also publishes its own Shopify apps.
- Yes. We work with merchants across North America, Europe, and Asia. Our process is async-first with scheduled overlap hours, so time zones have never been a blocker for a project.
- It depends on scope. Theme customisations typically start in the low four figures; custom apps and full builds range higher. After a free audit we give you a fixed quote — no hourly surprises.
- Absolutely. Private (custom) apps are a large part of our work — from small workflow automations to full ERP integrations that only your store uses.
- Every project ships with a support window, and most clients stay on a maintenance retainer: monitoring, Shopify update compatibility, and a direct line to the same developers who built it.
- Continuity and coverage. An agency gives you review processes, backup when someone is unavailable, and accumulated platform experience across many stores — with one point of accountability.
Didn't Got your answer?
If you still have questions or need further assistance, feel free to reach out to our team. We're here to help you every step of the way.
Our talk
Let's Connect!
Tell us your requirements and get a professional developer response.




