Modern server rack with blue lighting in a secure data center

Shopify Apps Need IP Allowlists by Nov 20

November 6, 2026 · 14 min read · By Thomas A. Anderson

On November 12, 2026 at 11:59 PM ET, bulk mutations from public Shopify apps without an IP allowlist simply stop executing. Eight days later, the app gets delisted. But here is the counter-intuitive risk that should worry you more: an allowlisted ASN lets a stolen token be replayed from any tenant on that provider’s network. Which model do you pick? That choice decides whether your app survives the deadline, and how wide you open the door.

Key Takeaways:

  • Bulk queries and bulk mutations from public apps without an IP allowlist stop executing on November 12, 2026 at 11:59 PM ET. Delisting and the block on new installs land eight days later, on November 20, 2026.
  • IP mode accepts up to 100 addresses or ranges, with IPv4 no wider than /16 and IPv6 no wider than /32. ASN mode accepts up to 10 autonomous system numbers limited to up to 10 countries.
  • An allowlisted ASN covers the hosting provider’s whole network, so a stolen offline access token can be replayed from any other tenant on that network in an allowed country. Shopify’s own docs say to allowlist IP addresses when you can.
  • Shopify returns 403 both for requests outside the allowlist and for requests whose source address it cannot confirm, which is why Flow “Send HTTP request” actions and Oxygen-hosted storefront GraphQL calls cannot use the allowlist at all.
  • Custom apps and apps created by merchants are out of scope. Existing installs of non-compliant public apps keep working until Admin API requests are rejected on April 1, 2027.

Both dates require the same configuration screen, in the Dev Dashboard, and that screen forces a design decision that is easy to get wrong. Static IPs and CIDR ranges give you the narrowest boundary. ASN plus country rules are the only entry type that survives a serverless platform rotating egress addresses under you. The two are separate security boundaries with different tradeoffs, and neither is simply the stronger version of the other.

What the Allowlist Actually Checks

When enforcement is on, Shopify inspects the source IP address of every GraphQL Admin API and REST Admin API request that uses your app’s tokens, as the Dev Dashboard documentation describes. Requests from outside your allowed addresses fail with 403, and so do requests whose source address Shopify cannot confirm.

The practical effect is on credential replay. A stolen offline access token stops working from anything other than the networks you listed, which is the failure mode the control is aimed at: leaked tokens can be used from any network location by default. The token itself is not revoked and does not become invalid. It stays valid, and it will work from any allowed address. That distinction matters when you write your incident runbook, because “revoke the token” and “the request came from the wrong network” call for different responses.

Scope matters before you spend a sprint on this. The requirement covers all public apps. Custom apps are unaffected, and so are apps merchants create themselves in the Dev Dashboard or in admin, per the requirement post. Shopify still recommends allowlists for custom apps, but the deadline columns do not apply to them. Two request types are never rejected: dev preview requests on dev stores, and App Bridge direct API access from your app’s embedded pages.

Static IP and CIDR: The 100-Entry Budget

IP mode takes up to 100 addresses or ranges, one per line. Each is a CIDR range, meaning an address plus a prefix length; an entry without a prefix is a single address. The rules that trip people up are the width limits and the public-address rule:

  • IPv4 must be /16 or narrower. IPv6 must be /32 or narrower.
  • Public addresses only. If your servers sit behind a NAT gateway, you list the gateway’s addresses, not the instances’ private ones.
  • Each range must start at its network address, so 203.0.113.0/24 and not 203.0.113.7/24.
  • If your servers reach Shopify over IPv6, include their IPv6 addresses too.

A hundred entries is generous for a fixed fleet: a couple of regions, blue/green pairs, a failover host, and a NAT gateway per availability zone fits comfortably. That budget falls apart as soon as the platform rotates egress on every deploy. Serverless functions and container platforms that churn outbound addresses will burn through a range list faster than anyone can maintain it, and the failure mode is a 403 on a merchant’s order sync at 2 AM.

The inventory step is where most of the work lives. Pull the client IP addresses of your app’s requests from the Logs view in the Dev Dashboard, then reduce them to distinct addresses with counts:

Two services behind one NAT gateway collapse into a single address here, which is fine for the allowlist but hides which service broke when a 403 appears.

Then check each candidate range against what you actually observed before you save anything:

#!/usr/bin/env python3
# check_egress.py - compare observed egress addresses against a proposed allowlist.
# usage: python3 check_egress.py allowlist.txt observed-ips.log
import ipaddress
import sys

def load_ranges(path):
 nets = []
 for line in open(path):
 line = line.strip()
 if not line or line.startswith("#"):
 continue
 nets.append(ipaddress.ip_network(line, strict=True))
 return nets

def load_observed(path):
 addrs = set()
 for line in open(path):
 parts = line.split()
 if parts:
 addrs.add(ipaddress.ip_address(parts[-1] if len(parts) == 1 else parts[0]))
 return addrs

nets = load_ranges(sys.argv[1])
observed = load_observed(sys.argv[2])

outside = sorted(a for a in observed if not any(a in n for n in nets))
print(f"{len(observed)} observed addresses, {len(nets)} ranges, {len(outside)} unmatched")
for addr in outside:
 print(f" OUTSIDE {addr}")

# Note: strict=True rejects ranges that do not start at the network address,
# which is exactly the mistake Shopify's docs warn about. Production use should
# also flag IPv4 ranges wider than /16 and any private or reserved address.

ASN Plus Country Rules and the 10/10 Limits

If your egress is dynamic, Shopify offers the Networks (ASN) option, which the serverless-hosting changelog describes as support for apps without static IP addresses. You enter up to 10 autonomous system numbers, one per line, as a number with or without the AS prefix. Private, reserved, and documentation ASNs are rejected. You then choose up to 10 countries, which Shopify recommends: without a country list, an allowed ASN is allowed from anywhere.

Shopify resolves the ASN and country of each source address through MaxMind’s GeoLite data. A request passes when its ASN is on your list and, if you listed countries, its country is on your list too. If the data has no ASN for an address, or no country when you listed countries, the request is rejected. Country means where the data places the address, not where the network’s owner is registered, so a provider’s corporate headquarters is irrelevant to the outcome.

Two operational details follow from that. A provider can run more than one ASN and can add more over time, so a single entry is not a permanent answer; Shopify says to check your provider’s documentation or derive the ASNs from client IPs in your Dev Dashboard logs. And a worked example helps ground the numbers: Google Cloud’s ASN is 396982, listed by PeeringDB as GOOGLE-CLOUD-PLATFORM, which is the entry a platform like Gadget documents for its own Shopify app. If you moved from a fixed fleet to a serverless platform, your 30 IP ranges become one AS number and a country list.

Why an ASN Is Shared, Not Dedicated

An ASN identifies one network run by one organization, such as a hosting provider or an ISP. It does not identify your tenancy inside that network. Shopify’s documentation puts the consequence in a caution block: a provider’s network is shared with its other customers, so anyone who runs servers in an ASN you allow, and who has a stolen token, can use it from there. The docs go further and say to allowlist only IP addresses when you can, and if you allowlist ASNs, to list only the countries your app runs in.

The country list is the compensating lever, and it applies to every ASN on the list rather than to one entry at a time. Gadget’s setup guide spells out the implication: adding Germany for your own server also admits Google Cloud addresses in Germany. If your app runs in one region, that constraint is cheap. If it runs in five countries, each country you add widens the shared-tenant surface by roughly the population of other customers on that ASN in that country, which is not a number you can measure from the Dev Dashboard.

Dimension IP addresses and CIDR ranges ASN plus countries
Entry limit Up to 100 addresses or ranges Up to 10 ASNs and up to 10 countries
Width or syntax rules IPv4 /16 or narrower, IPv6 /32 or narrower Public ASN number, with or without AS prefix
Stability under redeploy Ranges go stale when a platform rotates egress addresses Entry stays valid as individual addresses change, which is why Shopify offers it for dynamic egress
Who else can replay a stolen token Only hosts inside your listed ranges Any host on an allowed ASN in an allowed country
Setup effort Enumerate ranges per workload, revisit when the network changes Enumerate ASNs plus countries, then re-test when the provider adds a network

One mechanical constraint catches teams mid-migration. Gadget’s guide notes that the allowlist holds IP addresses or ASNs and not both, so saving an ASN entry replaces any IP ranges already configured. If you have a static-IP production fleet and one serverless worker, you cannot express that split in a single app’s allowlist. Either move the worker onto fixed egress through a NAT gateway or proxy, or move the whole app to ASN mode and accept the wider boundary. Fixed-egress infrastructure has its own line item, and the reasoning is the same one in our guide to TCO modeling for infrastructure decisions: you are paying to own a network identity rather than rent a shared one.

Deadlines, Testing, and the 403 You Will Hit

The staged enforcement in Shopify’s November 2026 security update separates the bulk-operations date from the general one by eight days, and pushes the hard cutoff into 2027.

Date What changes for a public app without an allowlist
November 12, 2026, 11:59 PM ET Bulk queries and bulk mutations fail to execute
November 20, 2026, 11:59 PM ET Delisted from the App Store, blocked from new installs, flagged in merchant Settings > Apps
January 15, 2027 Merchants see a warning in their Shopify admin
April 1, 2027 GraphQL Admin API and REST Admin API requests are rejected

Existing installs keep working through all of it until the final row. Keep that in mind for anyone who reads November 20 as “the API goes dark.” The API goes dark for non-compliant apps on April 1, 2027.

The setup path runs entirely through the Dev Dashboard UI. Open your app, click Settings, click Manage in the IP allowlist section, then choose IP addresses or Networks (ASN). Enter the entries, click Run test under Test against recent traffic, and check whether any of your own addresses from the last 30 days would be blocked. Results can take a few minutes to include the most recent requests. Then turn on Enforcement, click Save, and confirm a request from outside the allowlist fails.

That last step is the one people skip. Write the assertion down explicitly and run it from every environment after enforcement is on:

#!/usr/bin/env bash
# Assert the allowlist actually blocks an unlisted source.
# Run from a machine you know is NOT in the allowlist, or from a runner in
# a different region. Token must be a real token for a dev store.
set -uo pipefail

SHOP_DOMAIN="your-dev-store.myshopify.com"
API_VERSION="2026-01" # set to the version your app pins
TOKEN="shpat_..." # never commit this; read from a secret store

status=$(curl -s -o /tmp/body.json -w '%{http_code}' \
 -X POST "https://${SHOP_DOMAIN}/admin/api/${API_VERSION}/graphql.json" \
 -H "Content-Type: application/json" \
 -H "X-Shopify-Access-Token: ${TOKEN}" \
 -d '{"query":"{ shop { name } }"}')

echo "HTTP ${status}"
if [ "${status}" = "403" ]; then
 echo "OK: allowlist is blocking unlisted sources"
else
 echo "FAIL: expected 403, got ${status}. Enforcement is off or the source is allowed."
 exit 1
fi

# Note: this verifies the boundary from one location only. Run it from every
# region and every workload that calls the Admin API, and treat a 200 from a
# machine that should be outside as a misconfigured allowlist, not a pass.

A 403 immediately after enabling enforcement is almost always infrastructure, not auth. Check whether failures started at the moment you saved the setting, whether one service is succeeding while another fails, and whether the same token still works from the host you expected. The common cause is a workload whose egress nobody modeled: a queue consumer on its own host, a scheduled job on a different machine than the web server, a blue/green pair, a second region, a failover box, or a NAT gateway that changed addresses during a migration. Rotating a healthy token to “fix” a 403 is the wrong move and costs you merchant re-auth.

Choosing a Model for Your Deployment

If every Admin API call leaves from an address you control and can enumerate, use IP mode and get the narrowest boundary Shopify supports. If egress is dynamic because serverless functions, container platforms, or an edge runtime assign addresses you do not own, use ASN mode with the smallest country list that covers your regions, and know that a static-egress proxy is the alternative if the shared-tenant risk is unacceptable.

Then handle the other half of the problem. Shopify’s guidance pairs the allowlist with expiring offline access tokens, which bound how long a leaked credential works, while the allowlist bounds where it works. The token track has its own schedule: public apps still using non-expiring offline tokens are delisted and blocked from new installs on January 1, 2027, with requests rejected from April 1, 2027. You can filter Dev Dashboard API logs by token type to find remaining non-expiring tokens.

Rollout Order for a Public App

Do the inventory before you touch the Dev Dashboard, and do the compatibility check before the inventory, because two request types cannot be allowlisted and will need architectural changes rather than a settings change. Shopify states plainly that access tokens used by the Shopify Flow Send HTTP request action are rejected, because the source IP cannot be confirmed. The same applies to tokens used for GraphQL Admin API requests from a storefront hosted on Oxygen. If either pattern is in your app, plan the move now, not on April 1, 2027.

  • Inventory every workload that makes GraphQL or REST Admin API calls: web servers, workers, queue consumers, scheduled jobs, regions, failover hosts, and developer laptops running scripts against production tokens.
  • Pick the model per app. Fixed egress goes to IP mode; dynamic egress goes to ASN plus countries. Remember one allowlist holds one or the other.
  • Run the 30-day test and trace every unlisted address back to a workload instead of adding it. An allowlist that contains every address you cannot explain has given up most of its value.
  • Enforce during a controlled window, then make a harmless Admin API query from every legitimate workload and confirm success, and the same query from outside and confirm 403.
  • Monitor 403s from your own production hosts. An unexpected one from an approved environment means a missing entry or a changed network path, and Shopify recommends reviewing API logs on a schedule for addresses you do not expect.
  • Document the allowlist as an infrastructure dependency, so the next NAT gateway change or region migration includes it in the change plan.

For teams that also carry platform churn on the app side, the allowlist work sits in the same maintenance bucket as the API changes covered in our analysis of Shopify’s native mobile rebuild: a dated, vendor-driven requirement that costs less the earlier it enters the sprint plan. The blast radius is what sets this apart. A missed API version deprecation breaks a feature; a missed allowlist deadline stops bulk operations on November 12 and then the entire Admin API in April 2027.

More in-depth coverage from this blog on closely related topics:

Sources and References

Sources cited while researching and writing this article:

Thomas A. Anderson

Mass-produced in late 2022, upgraded frequently. Has opinions about Kubernetes that he formed in roughly 0.3 seconds. Occasionally flops, but don't we all? The One with AI can dodge the bullets easily; it's like one ring to rule them all... sort of...