If a GST invoice went missing on an order last week, open the Developer Dashboard at dev.shopify.com, pick the custom app, and go to the logs. Filter by HTTP status, set it to anything that isn't 2xx, and narrow the date range to the day the order was placed. Shopify started surfacing filterable request logs, API error rates, webhook delivery health and admin page load times for custom apps there in late September 2026, which means the answer to "why did this order not get an invoice" is now a two-minute lookup instead of a three-hour archaeology dig through your own server logs. That's the whole post in one paragraph. The rest is about what to do with what you find.
Where your custom app logs live, and the one thing that trips people up
Custom apps come from two different places and people forget which one they used. If the app was created in the store admin under Settings → Apps and sales channels → Develop apps, it lives in that store and that's where you manage its scopes and tokens. If it was created in the Developer Dashboard (or the older Partner Dashboard) and installed to a single store, it shows up in the dashboard with the full metrics view attached.
We get this wrong on the first pass more often than we'd like, usually on a store where a freelancer built the invoicing integration three years ago and nobody recorded where. If the app isn't in the dashboard, the fast check is the access token prefix in your environment file: tokens issued through the admin's Develop apps flow start differently from OAuth-issued ones. Find that, and you know which surface to look at.
Once you're in, the four things worth your attention are the request log, the API error rate chart, webhook delivery health, and the load times for your embedded admin pages. Everything below hangs off those.
The quiet failure: an orders/create webhook that never got a 200
Here's the shape of the problem for an Indian store. An order comes in. Shopify fires orders/create to your custom app. The app pulls the line items, works out the HSN codes and the CGST/SGST or IGST split based on the ship-to state, generates a sequential invoice number, writes the PDF, and emails it. If you're above the e-invoicing threshold it also calls the IRP for an IRN before any of that is valid.
Now the app's server is on a small VPS and your Diwali sale pushes 3,200 orders through in a week. During a two-hour window on the second evening the box runs out of memory and starts returning 503. Shopify retries, the box is still unhealthy, the retries expire. Sixty-one orders end up with no invoice.
Nobody notices. The customer got their order confirmation from Shopify, which has nothing to do with your app. Shipping went out fine, because Shiprocket pulls from a different integration. The gap surfaces on the 10th of the following month when your accountant is reconciling for GSTR-1 and finds the invoice series jumps from 2026-27/0891 to 2026-27/0953. By then nineteen of those sixty-one are COD refusals that have come back as RTO, and your team is trying to raise credit notes against invoices that were never issued.
That's a sequential numbering problem, a reconciliation problem and a customer service problem, all from one unhealthy server for two hours. The webhook delivery health panel would have shown it the same evening.
How Shopify retries a failed webhook delivery, and when it stops trying
Three mechanics matter.
First, your endpoint has five seconds to return a 2xx. Five. If you generate the PDF, call the IRP, push to your mailer and then respond, you will blow that budget on a slow night. The correct pattern is: verify the HMAC, drop the payload on a queue, return 200, do the work asynchronously. We rewrite invoicing apps this way more often than we build them from scratch.
Second, Shopify retries failed deliveries over roughly a 48-hour window, with the interval widening after each attempt. So a thirty-minute outage costs you nothing as long as the endpoint comes back. A two-day outage costs you every order in it.
Third, if every attempt in that window fails, Shopify removes the subscription. This is the one that burns people. Your app doesn't just miss the orders from the outage; it stops receiving orders/create entirely and keeps running happily, processing nothing. Six weeks later someone asks why the invoice folder is empty. Check your registered webhook subscriptions as part of the same routine you use to check the logs — if a topic you expect to see isn't in the list, that's your answer.
Fixing a failed webhook delivery properly
In order of how often it's actually the cause:
- Timeout, not error. The endpoint is doing synchronous work. Move it behind a queue. This is at least half the failed-delivery tickets we see.
- HMAC verification failing after a secret rotation. You rotate the client secret, forget one environment, and staging starts 401-ing production traffic. The log shows a clean 401 with a consistent pattern, which makes it easy to spot.
- The host is fine but the app isn't. A payload with an unexpected shape — a free gift line item with zero price, a Hindi-script product title, an address with no PIN code because the order came through a channel that doesn't collect one — throws an unhandled exception and returns 500 for that one order while everything else works. These are the worst, because the error rate chart stays near zero and the failure is invisible in aggregate.
- The subscription is gone. Covered above.
And the thing you should build regardless of how clean your webhooks look: a reconciliation job. Once an hour, query orders updated since the last run and check each one has an invoice record on your side. Webhooks are an optimisation, not a guarantee. For a store doing invoices under GST, reconciliation is the control that actually keeps the series intact. If you're commissioning custom app development, make this a line item in the scope rather than something you bolt on after the first bad month.
The 429 error, and the backfill job that ate your rate limit
A 429 from the Admin API means you emptied the bucket. On a standard plan the REST bucket holds 40 requests and refills at 2 per second; Shopify Plus raises that roughly tenfold. GraphQL works on a points budget with the same leaky-bucket idea.
Steady-state order volume almost never causes this. Suppose your invoicing app makes three calls per order: fetch the order with line items, write a metafield with the invoice number, add a tag. At 3,200 orders in a week that's 9,600 calls spread across seven days. Nowhere near the ceiling.
What causes 429s is the batch job. Someone kicks off a backfill to regenerate 4,000 invoices after a tax rate change. 4,000 calls at a sustained 2 per second is 2,000 seconds — just over 33 minutes, and that's the theoretical floor assuming nothing else is touching the API. Your fulfilment sync is also running. So is the app that pushes abandoned carts to WhatsApp. Everything starts getting 429s, including the live webhook handler trying to invoice real orders.
Three fixes. Read the rate limit header Shopify returns and throttle against it rather than guessing. Honour Retry-After on a 429 instead of retrying immediately, which is what naive HTTP clients do and which makes it worse. And run backfills at 4am, not during a sale.
The API error rate chart in the dashboard makes the pattern obvious: a flat line with a sharp block of 429s sitting over exactly the window someone ran a script. Correlate it with your own deploy and cron history and you'll usually find the culprit in under five minutes.
Shiprocket manifests: the second place orders disappear
The invoicing failure is slow and expensive. The fulfilment failure is fast and loud, and you'll hear about it from customers before you see it in a chart.
Typical setup: a custom app listens on orders/paid (or orders/create for COD, since COD orders never hit paid) and pushes the order to Shiprocket to create a shipment. The COD distinction is where we see most breakage. An app subscribed only to orders/paid silently ignores every cash-on-delivery order, which on a lot of Indian stores is a large share of the book. The orders sit in Shopify looking perfectly normal, unfulfilled, until someone eyeballs the list.
Second failure mode: the courier API returns a validation error — unserviceable PIN code, weight above the COD slab, address line too long — and your app logs it on your side but returns 200 to Shopify, because technically it received the webhook fine. Delivery health looks perfect. The order is still stuck.
So the dashboard logs tell you about transport failures between Shopify and your app. They tell you nothing about what happened after. You need your own error surface for the downstream call, and the cheapest useful version is a Shopify order tag: shiprocket-failed, written by the app whenever the courier call returns an error. Then a saved view in the admin filtered on that tag, and someone looks at it twice a day. Not elegant. Works.
Admin page load metrics, or why your embedded app takes nine seconds
The dashboard now reports load times for embedded admin pages, which is useful because nobody ever measured this before and a lot of custom apps are genuinely awful here. Your ops team opens the invoice app twenty times a day. Six seconds each way adds up to real salary.
The common causes, roughly in order:
- The page makes its API calls on first render instead of serving a shell and loading data after. Combine that with a GraphQL query pulling 250 orders with every field and you've built a nine-second blank screen.
- No pagination. An invoice list that renders every order since launch will get slower every month until someone complains.
- App Bridge loading late, so the iframe sits there before the frame even handshakes with the admin.
- Server in the wrong region. If the app runs on a box in Virginia and your team is in Bengaluru, you're paying 200ms+ on every round trip before anything else happens. For a store operating mostly in India, host in Mumbai or Singapore.
None of this touches your storefront speed, which is a separate problem with separate fixes. If customer-facing load time is the thing keeping you up, start with storefront performance instead — admin page load affects your staff, not your conversion rate.
An eleven-minute weekly routine
Put it in a calendar invite. Monday morning, before the week eats you.
- Webhook delivery health for the last 7 days. Any topic below about 99% delivered gets investigated, not noted.
- The subscription list. Confirm every topic you expect is still registered.
- API error rate. Look for blocks of 429s and 5xx clusters. Match them against your deploy log.
- Filter the request log to non-2xx and read the actual payloads of three failures. Not the counts — the payloads. That's where you find the zero-price gift item and the missing PIN code.
- Invoice series check: last invoice number against order count for the week. If the arithmetic doesn't match, you have gaps.
- Your shiprocket-failed tag view. Should be empty.
From mid-September, do it daily. The festive peak is when your cheap VPS falls over and when an invoice gap costs the most to unwind, because the returns from that period land in a different month than the sales.
Questions we get asked
Can I monitor a custom app from the Shopify admin instead of the dashboard? Partially. The admin shows you the app, its scopes and its token. Request-level logs, error rates and webhook health live in the Developer Dashboard. For day-to-day operational checks the admin isn't enough, and the honest answer is that your team needs dashboard access or someone who has it sending them a weekly screenshot.
How do I debug a legacy private app? Private apps were deprecated and replaced by custom apps, so if you're still running something on a private app key, that's the migration to do before anything else. Once it's a custom app with a proper access token, it gets the same logs and metrics as everything else. Until then you're relying on whatever logging the original developer wrote, which in our experience is a print statement and hope.
Can I replay a webhook Shopify gave up on? Not from Shopify's side. You re-fetch the affected orders through the Admin API and reprocess them yourself, which is exactly why the hourly reconciliation job earns its keep. For high-stakes flows, consider delivering webhooks to Amazon EventBridge or Google Pub/Sub rather than straight to an HTTP endpoint — the queue absorbs an outage that an HTTP handler can't.
Do these metrics cover public apps from the App Store? No. If a third-party app is dropping your orders, you're filing a support ticket with that developer. Log in your own app whatever evidence you can — timestamps, order IDs, what you expected to happen — because the first thing they'll ask for is reproduction steps.
Start with one filter
Open the logs for your invoicing app, set the date range to the last 30 days, filter to non-2xx responses, and count them. If the number is zero, your reconciliation job is the next thing to build. If it isn't zero, you now know exactly how many orders went out without a valid invoice last month, and you can go and fix those before the next filing date.
If you'd rather someone else did the reading, our team in Bengaluru does this as a fixed-scope audit of your custom apps and webhook flows. Ask for an audit, or bring in a developer for the queue-and-reconcile rebuild if you already know that's what you need.


