Error Medic

Fixing Shopify Rate Limit (HTTP 429) and API Connection Errors (4xx, 5xx)

Resolve Shopify API rate limits (HTTP 429), 401/403 access issues, 5xx server errors, and webhook timeouts. Learn to implement leaky bucket and backoff logic.

Last updated:
Last verified:
1,737 words
Key Takeaways
  • Exceeding the REST API leaky bucket limit (default 40 calls/app, replenishes at 2/sec) triggers HTTP 429 Too Many Requests errors.
  • GraphQL API limits are cost-based (1000 points maximum, 50 points/sec replenishment), requiring query optimization to prevent rate limiting.
  • Webhook timeouts and drops occur when your server takes longer than 5 seconds to respond with a 200 OK status.
  • Implement exponential backoff with jitter and respect the Retry-After header to dynamically handle 429s and 5xx (500, 502, 503) errors.
Shopify API Throttling & Retry Approaches Compared
MethodWhen to UseTime to ImplementRisk Profile
Header-Based ThrottlingREST API standard syncs (watching X-Shopify-Shop-Api-Call-Limit)LowLow (Prevents 429s proactively)
Exponential Backoff + JitterHandling 500, 502, 503 errors and unexpected 429sMediumLow (Standard best practice)
GraphQL Bulk OperationsExporting/Importing > 5,000 products or ordersHighLowest (Asynchronous, no limits)
Asynchronous Webhook QueuesFixing 'webhook not working' / timeout issuesMediumLowest (Guarantees 200 OK under 5s)

Understanding the Error: The Anatomy of Shopify API Limits

When scaling a Shopify application or custom integration, encountering API limits and connection errors is inevitable. Shopify protects its infrastructure using strict rate-limiting algorithms. If your app requests data too aggressively, fails to authenticate properly, or encounters temporary server instability, you will be hit with a barrage of HTTP 4xx and 5xx errors.

Understanding the nuanced differences between a Shopify 429 Rate Limit, a Shopify 401/403, and Shopify 500/502/503 server errors is critical for building resilient integrations.

The Leaky Bucket (REST) vs. Calculated Cost (GraphQL)

Shopify employs two different rate-limiting mechanisms depending on the API you are consuming:

  1. REST Admin API (The Leaky Bucket): The standard limit is 40 requests per app, per store. This "bucket" empties (replenishes) at a rate of 2 requests per second. If you burst 40 requests simultaneously, your 41st request will fail with an HTTP 429 Too Many Requests error. Shopify Plus stores double this limit (80 bucket size, 4/sec replenishment).
  2. GraphQL Admin API (Calculated Cost): GraphQL uses a point system. The maximum bucket size is 1,000 points, replenishing at 50 points per second (Shopify Plus is 2,000 points, 100/sec). Complex queries asking for nested relational data (e.g., Orders -> Line Items -> Product Variants) cost more points than simple flat queries.

Diagnosing Specific Status Codes

HTTP 429: Too Many Requests

This is the classic rate limit error. When you receive a 429, Shopify will include a Retry-After header in the response. This header explicitly tells your application how many seconds it must wait before making another request. Ignoring this header and continuing to hammer the API can result in temporary app suspension.

HTTP 401 Unauthorized & HTTP 403 Forbidden

Authentication and authorization errors often manifest during token rotation or scope changes.

  • 401 Unauthorized: Your access token is invalid, expired, or corrupted. This often happens if the merchant uninstalls and reinstalls the app, invalidating the previous OAuth token.
  • 403 Forbidden: Your token is perfectly valid, but your app lacks the required access scopes to perform the action. For instance, trying to read customer data without the read_customers scope in your OAuth flow will trigger a 403.
HTTP 500, 502, 503, and Timeouts

These are upstream errors on Shopify's side, often caused by database locks, localized network partitions, or massive platform-wide traffic spikes (like Black Friday).

  • 500 Internal Server Error: An unexpected condition was encountered by Shopify.
  • 502 Bad Gateway: The API gateway received an invalid response from Shopify's internal upstream servers. Often happens during heavy data imports.
  • 503 Service Unavailable: Shopify is temporarily down or overloaded.
  • Shopify Timeout (504 Gateway Timeout): Your query took too long to execute. This is common in complex GraphQL queries filtering on unindexed metafields.
Shopify Webhook Not Working

If you are wondering why your webhooks are mysteriously failing or being dropped, it is almost always a timeout issue. Shopify requires your server to acknowledge a webhook receipt with an HTTP 200 OK within exactly 5 seconds. If your application attempts to process the payload (e.g., resizing images, updating databases) before responding, you will likely exceed this window. Shopify will consider the webhook failed and attempt retries, eventually dropping the webhook subscription entirely.


Step 1: Diagnose Your Current API Usage

Before implementing a fix, you must diagnose how you are hitting the limits. For REST API calls, inspect the X-Shopify-Shop-Api-Call-Limit header returned in every successful response.

It looks like this: X-Shopify-Shop-Api-Call-Limit: 38/40.

This tells you that you have used 38 out of your 40 available slots. If you see this number approaching the maximum, your application is running too hot.

For GraphQL, inspect the extensions.cost object returned in the JSON payload:

"extensions": {
  "cost": {
    "requestedQueryCost": 102,
    "actualQueryCost": 40,
    "throttleStatus": {
      "maximumAvailable": 1000,
      "currentlyAvailable": 960,
      "restoreRate": 50
    }
  }
}

Step 2: Implement Proactive Throttling (REST)

The most efficient way to handle 429s is to prevent them entirely. Instead of waiting for a 429 error to occur, your HTTP client should read the X-Shopify-Shop-Api-Call-Limit header on every response.

If the ratio of used calls to total calls exceeds a safe threshold (e.g., 85%), your application thread should automatically sleep for 1-2 seconds to allow the bucket to replenish before firing the next request. This is far more efficient than catching exceptions and retrying.

Step 3: Implement Exponential Backoff with Jitter (5xx & 429s)

For unexpected 429s (e.g., multiple concurrent worker nodes hitting the API simultaneously without a distributed lock) and all 5xx errors, you must implement exponential backoff.

When a request fails with a 429, 500, 502, or 503:

  1. Check for the Retry-After header. If present, sleep for that exact amount of time.
  2. If Retry-After is missing (common with 5xx errors), wait for a base delay (e.g., 1 second) and retry.
  3. If the retry fails, double the delay (2s, 4s, 8s) up to a maximum threshold.
  4. Crucial: Add "jitter" (randomized variance) to the delay time. If 50 background workers all encounter a 502 and retry at exactly the same millisecond 2 seconds later, they will create a thundering herd that knocks the service down again.

Step 4: Fix Failing Webhooks (The Queue Pattern)

To fix the "Shopify webhook not working" issue, decouple receipt from processing.

When your endpoint receives a webhook payload via POST:

  1. Validate the HMAC signature immediately to ensure it came from Shopify.
  2. Push the raw JSON payload into an asynchronous message queue (e.g., AWS SQS, Redis/Celery, RabbitMQ).
  3. Immediately return an HTTP 200 OK back to Shopify.

This entire process takes less than 50 milliseconds, ensuring you never breach the 5-second timeout limit. A separate background worker can then pull the payload from your queue and perform the heavy database operations at its own pace.

Step 5: Migrate to Bulk Operations for Large Datasets

If you are regularly hitting limits because you are syncing thousands of products, orders, or customers, you are using the wrong API.

Switch to the Shopify GraphQL Bulk Operations API. This API allows you to submit a massive GraphQL query. Shopify processes it asynchronously on their backend without any rate limits. Once complete, Shopify sends a webhook containing a URL to a JSONL (JSON Lines) file containing all your data. This is the only scalable way to handle large enterprise-level ETL tasks on Shopify without constantly fighting HTTP 429 errors.

Frequently Asked Questions

python
import time
import random
import requests
from requests.exceptions import HTTPError, ConnectionError, Timeout

def shopify_request_with_retry(url, headers, max_retries=5):
    retries = 0
    
    while retries < max_retries:
        try:
            response = requests.get(url, headers=headers, timeout=10)
            
            # Proactive REST Rate Limiting Check
            call_limit_header = response.headers.get('X-Shopify-Shop-Api-Call-Limit')
            if call_limit_header:
                used, total = map(int, call_limit_header.split('/'))
                # If we've used 90% of our bucket, pause proactively
                if used / total > 0.90:
                    time.sleep(2.0)  # Wait for bucket to replenish

            response.raise_for_status() # Raise exception for 4xx/5xx
            return response.json()
            
        except (HTTPError, ConnectionError, Timeout) as e:
            status_code = response.status_code if response else None
            
            # Handle 429 Too Many Requests
            if status_code == 429:
                # Respect Retry-After header if provided by Shopify
                retry_after = float(response.headers.get('Retry-After', 2.0))
                print(f"[429] Rate limited. Sleeping for {retry_after}s...")
                time.sleep(retry_after)
                continue
                
            # Handle 5xx Server Errors (500, 502, 503, 504)
            elif status_code in [500, 502, 503, 504] or isinstance(e, (ConnectionError, Timeout)):
                retries += 1
                # Exponential backoff with jitter (e.g., 2, 4, 8, 16 seconds + random MS)
                sleep_time = (2 ** retries) + random.uniform(0, 1)
                print(f"[{status_code}] Server error. Retrying in {sleep_time:.2f}s (Attempt {retries}/{max_retries})")
                time.sleep(sleep_time)
                continue
                
            # Unrecoverable errors (401, 403, 404, etc.)
            else:
                print(f"Unrecoverable error {status_code}: {e}")
                raise e
                
    raise Exception(f"Failed after {max_retries} retries.")
E

Error Medic Editorial

A collective of Senior Site Reliability Engineers and DevOps practitioners dedicated to solving complex infrastructure and API integration challenges.

Sources

Related Articles in Shopify

Explore More API Errors Guides