Skip to content
SystemSyncer
Menu

Guide · Reliability

Shopify Webhooks vs. Polling for ERP Integrations

When to use Shopify webhooks, when to poll the GraphQL Admin API, and how to implement the hybrid pattern Shopify itself recommends for ERP integrations.

Last reviewed:
Reading time
4 min read
Intermediate

Short answer

Use webhooks for low-latency notification and scheduled polling for completeness. Shopify documents that webhooks can be delayed, duplicated, delivered out of order, and that subscriptions are removed after persistent failures, so it recommends reconciliation jobs. In practice, a Shopify–ERP integration should process webhooks as triggers and run an updated_at-based polling job (or bulk operation for large datasets) to catch anything missed.

Data flow Hybrid change detection
  1. Webhooks Fast
  2. Polling job updated_at checkpoint
  3. Job queue Deduplicate by resource
  4. Worker Fetch current state
  5. ERP

Both paths feed the same idempotent job queue, so an order found by both is processed once.

“Should we use webhooks or polling?” is the wrong question for a Shopify ERP integration. Shopify’s own guidance answers it: use webhooks, and don’t rely on them alone. This guide explains the reasoning in Shopify-specific terms and shows how to build the hybrid. For a platform-neutral decision framework, see the webhooks vs. polling comparison.

What each approach gives you on Shopify

Property Webhooks Polling the Admin API
Latency Seconds, typically Up to one polling interval
Completeness Not guaranteed; subscription can be removed after repeated failures Complete for the window queried, if the checkpoint logic is correct
Ordering Not guaranteed You control sort order (for example sortKey: UPDATED_AT)
Duplicates Possible; deduplicate with X-Shopify-Webhook-Id Overlapping windows return the same records; deduplicate by resource ID and updatedAt
Rate-limit cost None for delivery; API cost only when you fetch details Every poll costs query points
Infrastructure Public HTTPS endpoint, or EventBridge / Pub/Sub Scheduler only

The table shows why the two complement each other: webhooks are fast and cheap but incomplete; polling is complete but slower and costs API capacity.

The hybrid pattern

  1. Webhook path. Subscribe to the topics you need. The receiver verifies, stores, and acknowledges. A worker fetches current state and processes it.
  2. Polling path. A scheduled job queries resources updated since a stored checkpoint. For each resource, it enqueues the same job the webhook path would.
  3. One idempotent queue. Both paths create jobs keyed by resource ID (plus updatedAt, if you want to distinguish versions). A job that already exists or a version already processed is skipped.

Because the worker always fetches current state and writes idempotently, it does not matter which path found the change first, or whether both did.

Implementing the polling job

Checkpoint logic

Store a checkpoint per shop and resource type. On each run:

  1. Read the checkpoint T.
  2. Query resources with updated_at greater than T − overlap (a few minutes of overlap absorbs clock skew and in-flight writes).
  3. Page through all results.
  4. Enqueue jobs for each resource.
  5. Only after every page succeeds, set the checkpoint to the highest updatedAt seen (or the run’s start time).

If a run fails halfway, the checkpoint does not move and the next run repeats the window. Duplicates are harmless because the queue is idempotent.

async function pollUpdatedOrders(shop: string) {
  const checkpoint = await checkpoints.get(shop, 'orders');            // e.g. 2026-10-26T10:00:00Z
  const since = new Date(checkpoint.getTime() - 5 * 60_000).toISOString();
  let cursor: string | null = null;
  let maxSeen = checkpoint;

  do {
    const { data } = await shopify.graphql(RECENT_ORDERS, {
      query: `updated_at:>'${since}'`,
      cursor,
    });
    for (const o of data.orders.nodes) {
      await jobs.enqueueUnique({ shop, type: 'order', resourceId: o.id, version: o.updatedAt });
      if (new Date(o.updatedAt) > maxSeen) maxSeen = new Date(o.updatedAt);
    }
    cursor = data.orders.pageInfo.hasNextPage ? data.orders.pageInfo.endCursor : null;
  } while (cursor);

  await checkpoints.set(shop, 'orders', maxSeen);
}
query RecentOrders($query: String!, $cursor: String) {
  orders(first: 100, after: $cursor, query: $query, sortKey: UPDATED_AT) {
    pageInfo { hasNextPage endCursor }
    nodes { id updatedAt }
  }
}

Request only id and updatedAt in the poll. The worker fetches full details later; this keeps the polling query cheap.

Using bulk operations for large reconciliations

For full-catalog or full-inventory reconciliation, use bulkOperationRunQuery. Shopify runs the query asynchronously and gives you a URL to a JSONL file with all results. Subscribe to the bulk_operations/finish webhook topic (Shopify recommends this over polling the operation’s status) and process the file when it is ready. Bulk queries must complete within 10 days. Check the documentation for your API version on how many bulk operations can run concurrently — the limit changed in 2026-01.

Choosing the mix per workflow

Workflow Webhook topics Polling or bulk
Order export orders/create or orders/paid, orders/updated, orders/cancelled Poll orders by updated_at every 5–15 minutes
Refunds refunds/create Covered by order polling if you compare refund lists
Catalog changes made in Shopify products/update Nightly bulk export for drift checks
Inventory (ERP is source of truth) Optional inventory_levels/update to detect manual edits Nightly bulk export of levels for reconciliation
App lifecycle app/uninstalled Not applicable

Rate-limit planning

Polling consumes the same cost-based budget as everything else. Keep it predictable:

  • Poll narrow filters, not whole collections.
  • Request minimal fields.
  • Read the extensions.cost.throttleStatus in responses and slow down when capacity is low.
  • Schedule heavy reconciliation outside peak sales hours.

Testing the hybrid

  • Disable the webhook receiver for 30 minutes, place orders, re-enable it, and confirm the polling job exported every order exactly once.
  • Let both paths find the same order and confirm a single ERP order.
  • Kill the polling job mid-page and confirm the next run repeats the window without skipping records.
  • Shift the server clock by a few minutes and confirm the overlap window still catches every order.

Common mistakes

  • Advancing the checkpoint to “now” before processing finishes.
  • Polling full order details instead of IDs, burning rate-limit capacity.
  • Separate, non-idempotent queues for webhook and polling jobs.
  • No overlap window, so orders updated during a run are missed.

Summary

On Shopify, webhooks and polling are not alternatives — they are two halves of a reliable design. Webhooks deliver speed; a checkpointed updated_at poll (or a bulk operation for large datasets) delivers completeness. Route both into one idempotent queue, fetch current state in the worker, and the integration stays correct even when either path has a bad day.

Frequently asked questions

Is polling Shopify bad practice?

Polling everything at high frequency wastes rate-limit capacity. Polling a narrow, filtered query such as orders updated since a checkpoint is a normal reconciliation technique and is what Shopify means by reconciliation jobs.

How often should I poll?

Match the interval to the maximum delay your business can accept for an event the webhook path missed. Every 5 to 15 minutes is common for orders; hourly or nightly is common for catalog and inventory reconciliation.

Can I use only polling and no webhooks?

Yes, and for some ERPs that process in batches it is a reasonable choice. You lose low latency and use more API capacity, but you remove the public webhook endpoint.

When should I use bulk operations instead of polling?

When a reconciliation needs large result sets, such as all inventory levels or all products. Bulk queries run asynchronously and return a downloadable JSONL file, and they avoid paging through large connections under rate limits.

Sources and further reading

Platform-specific statements on this page were checked against these sources on . Vendor APIs change; confirm details in the current documentation before you build.

  1. [1] Webhook best practices (reconciliation jobs, ordering) — Shopify
  2. [2] Troubleshooting webhooks (retries, removal) — Shopify
  3. [3] orders query (updated_at filter) — Shopify
  4. [4] Bulk operations (queries) — Shopify
  5. [5] Shopify API rate limits — Shopify