Error Medic

Resolving Shopify Rate Limits (429) and API Errors (500, 502, 503, 401, 403): A Complete Troubleshooting Guide

Fix Shopify API rate limits (429 Too Many Requests), timeouts, and 5xx errors. Learn how to implement leaky bucket algorithms, handle webhooks, and resolve 401/

Last updated:
Last verified:
1,421 words
Key Takeaways
  • Shopify REST APIs use a leaky bucket algorithm; exceeding the bucket size or leak rate results in '429 Too Many Requests'.
  • GraphQL API limits are calculated based on query cost, requiring optimization of requested fields to prevent timeouts and 500/502/503 errors.
  • 401 (Unauthorized) and 403 (Forbidden) errors stem from invalid access tokens or missing access scopes in your app configuration.
  • Shopify webhooks have a strict 5-second timeout window; failing to respond with a 200 OK will cause retries and eventual webhook deletion.
  • Implement automatic retry mechanisms with exponential backoff by reading the 'Retry-After' header to handle rate limits gracefully.
Fix Approaches Compared
MethodWhen to UseTimeRisk
Exponential Backoff & RetryHandling persistent 429 Too Many Requests errors1-2 hoursLow
GraphQL Query OptimizationFixing 500/502/503 timeouts on complex data fetches2-4 hoursMedium
Asynchronous Webhook ProcessingWhen 'shopify webhook not working' or failing due to 5-second timeout2-3 hoursLow
OAuth Scope AuditingResolving 401 Unauthorized or 403 Forbidden errors30 minsHigh

Understanding Shopify API Errors

When scaling an application within the Shopify ecosystem, you will inevitably encounter a variety of HTTP errors. These generally fall into three categories: Rate Limiting (429), Server Errors / Timeouts (500, 502, 503), and Authentication/Authorization Issues (401, 403). Understanding the mechanics behind these errors is crucial for building resilient integrations.

The Leaky Bucket Algorithm and 429 Errors

Shopify's REST Admin API utilizes a 'leaky bucket' algorithm for rate limiting. Imagine a bucket that holds a maximum number of requests (bucket size) and leaks them at a constant rate (leak rate).

  • Standard Limit: 40 requests per app per store (bucket size), leaking at 2 requests per second.
  • Shopify Plus Limit: 80 requests per app per store, leaking at 4 requests per second.

When you burst requests and fill the bucket faster than it leaks, Shopify rejects subsequent requests with a HTTP 429 Too Many Requests status. The response body typically looks like this:

{"errors": "Exceeded 2 calls per second for api client. Reduce request rates to resume uninterrupted service."}

GraphQL Cost-Based Limits

Unlike REST, the GraphQL Admin API uses a calculated query cost. You are allotted a maximum of 1,000 cost points, restoring at 50 points per second. Complex queries with deep nesting or large connections consume more points. If a query exceeds the available points, you receive a Throttled error. Additionally, extremely complex queries might simply time out, resulting in a 502 Bad Gateway or 504 Gateway Timeout from Shopify's load balancers.

Webhook Failures

When dealing with "Shopify webhook not working" complaints, the root cause is almost always the 5-second timeout rule. When Shopify fires a webhook, your server must respond with a 200 OK within 5 seconds. If your application attempts to process the payload synchronously (e.g., updating a database, sending an email) and takes longer than 5 seconds, Shopify considers the delivery failed. It will retry up to 19 times over 48 hours. If all retries fail, the webhook subscription is automatically deleted.


Step-by-Step Troubleshooting and Resolution

1. Resolving 429 Too Many Requests

To prevent 429 errors, your application must respect the limits broadcasted in the response headers.

Actionable Steps:

  • Inspect Headers: Every REST API response includes the X-Shopify-Shop-Api-Call-Limit header (e.g., 39/40). Monitor this ratio.
  • Read Retry-After: When a 429 occurs, Shopify returns a Retry-After header indicating how many seconds to wait before trying again.
  • Implement Exponential Backoff: If you hit a 429, pause execution, read the Retry-After value (or default to a few seconds), and retry the request. If it fails again, double the wait time.

2. Mitigating 500, 502, and 503 Timeouts

Server errors in the 5xx range often mean your request was too heavy for Shopify to process in a timely manner. While 503 Service Unavailable can indicate a legitimate Shopify outage, 500 Internal Server Error and 502 Bad Gateway on specific endpoints usually point to unoptimized queries.

Actionable Steps:

  • Paginate Everything: Never attempt to fetch thousands of resources at once. Use cursor-based pagination (page_info) for REST and after cursors for GraphQL.
  • Reduce Payload Size: Use the fields parameter in REST (e.g., GET /admin/api/2023-10/products.json?fields=id,title) to return only necessary data. In GraphQL, only query the specific nodes you need.
  • Bulk Operations: For massive data exports or imports, abandon synchronous API calls and use the GraphQL BulkOperation API, which processes asynchronously and avoids timeouts.

3. Fixing Webhook Delivery Issues

To ensure reliable webhook delivery and prevent timeouts:

Actionable Steps:

  • Acknowledge Immediately: Your webhook endpoint should instantly return an HTTP 200 upon receiving the request.
  • Process Asynchronously: Offload the actual processing to a background worker queue (e.g., Celery, Sidekiq, Redis Queue, AWS SQS). The webhook handler's only job should be verifying the HMAC signature and pushing the payload to the queue.

4. Correcting 401 Unauthorized and 403 Forbidden

These are auth-related but have distinct meanings.

  • 401 Unauthorized: Your access token is missing, malformed, or has expired/been revoked by the merchant.
  • 403 Forbidden: Your token is valid, but your application lacks the necessary OAuth scopes to perform the action. For instance, trying to write products with only read_products scope.

Actionable Steps:

  • Verify Token: Ensure the X-Shopify-Access-Token header is present and correctly formatted.
  • Audit Scopes: Check your app's requested scopes during the OAuth flow. If you added new functionality, you must prompt the merchant to re-authenticate and accept the updated scopes.

Frequently Asked Questions

python
import requests
import time
import logging

logger = logging.getLogger(__name__)

def make_shopify_request_with_retry(url, headers, max_retries=5):
    """
    Makes a GET request to the Shopify API with exponential backoff 
    to handle 429 Too Many Requests errors.
    """
    retries = 0
    backoff_factor = 2

    while retries < max_retries:
        try:
            response = requests.get(url, headers=headers)
            
            # If successful, return the data
            if response.status_code == 200:
                return response.json()
            
            # Handle Rate Limiting
            elif response.status_code == 429:
                # Check for Retry-After header, default to backoff math if missing
                retry_after = int(response.headers.get('Retry-After', backoff_factor ** retries))
                logger.warning(f"Rate limited. Retrying in {retry_after} seconds...")
                time.sleep(retry_after)
                retries += 1
            
            # Handle Server Errors (500, 502, 503, 504)
            elif response.status_code >= 500:
                sleep_time = backoff_factor ** retries
                logger.warning(f"Server error {response.status_code}. Retrying in {sleep_time} seconds...")
                time.sleep(sleep_time)
                retries += 1
                
            # Handle Auth Errors (401, 403)
            elif response.status_code in (401, 403):
                logger.error(f"Authentication/Authorization error {response.status_code}: Check scopes and token.")
                response.raise_for_status() # Fail fast for auth issues
                
            else:
                # Unhandled client error
                response.raise_for_status()
                
        except requests.exceptions.RequestException as e:
            logger.error(f"Network error occurred: {e}")
            time.sleep(backoff_factor ** retries)
            retries += 1
            
    logger.error("Max retries exceeded.")
    return None

# Example Usage:
# headers = {"X-Shopify-Access-Token": "shpat_...", "Content-Type": "application/json"}
# data = make_shopify_request_with_retry("https://your-store.myshopify.com/admin/api/2023-10/products.json", headers)
E

Error Medic Editorial

The Error Medic Editorial team comprises Senior Site Reliability Engineers and DevOps professionals specializing in e-commerce infrastructure, high-throughput API integrations, and robust system architecture.

Sources

Related Guides