AnyLearn
All lessons
Businessintermediate

APIs, Webhooks, and Choosing the Right Tool

When Flow and Functions run out, you reach for the API. This lesson covers the Admin GraphQL API and its calculated-cost rate limiting, webhooks and why at-least-once delivery forces you to build for duplicates, bulk operations for large datasets, and a decision framework for choosing among all four approaches.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 7

The third surface

Two walls remain from Lesson 1. Flow cannot reach systems Shopify does not know about, and it cannot do bulk work. Functions cannot either; they answer one bounded question inside checkout.

Everything else runs through the API, and that means writing a real integration. This lesson covers what that involves and, more importantly, how to decide whether you should.

Three pieces make up the surface:

  • The Admin GraphQL API: read and write store data programmatically.
  • Webhooks: have Shopify tell your system when something happens, rather than asking repeatedly.
  • Bulk operations: move large datasets without fighting rate limits.

A warning worth giving up front. This is the most powerful option and the most expensive one. Flow is a workflow you can read in the admin. A Function is a bounded decision. An API integration is software you now own: it needs hosting, monitoring, error handling, and someone to fix it at 2am when it breaks during a sale. That cost is permanent and it is routinely underestimated.

So the real skill this lesson teaches is not the API. It is knowing when not to use it, which is why it ends in a decision framework rather than more syntax.

Full lesson text

All 7 steps on one page, for reading, reference, and search.

Show

1. The third surface

Two walls remain from Lesson 1. Flow cannot reach systems Shopify does not know about, and it cannot do bulk work. Functions cannot either; they answer one bounded question inside checkout.

Everything else runs through the API, and that means writing a real integration. This lesson covers what that involves and, more importantly, how to decide whether you should.

Three pieces make up the surface:

  • The Admin GraphQL API: read and write store data programmatically.
  • Webhooks: have Shopify tell your system when something happens, rather than asking repeatedly.
  • Bulk operations: move large datasets without fighting rate limits.

A warning worth giving up front. This is the most powerful option and the most expensive one. Flow is a workflow you can read in the admin. A Function is a bounded decision. An API integration is software you now own: it needs hosting, monitoring, error handling, and someone to fix it at 2am when it breaks during a sale. That cost is permanent and it is routinely underestimated.

So the real skill this lesson teaches is not the API. It is knowing when not to use it, which is why it ends in a decision framework rather than more syntax.

2. The Admin GraphQL API and calculated cost

Shopify's Admin API is GraphQL, and its rate limiting is the part that shapes how you write code against it. Most APIs count requests. Shopify counts work.

The model is a calculated query cost on a leaky bucket. Your app has a bucket of points that refills over time. Each query costs points according to how much it asks for, so requesting one product costs far less than requesting a thousand products with all their variants.

The accounting has a detail worth knowing:

  • Before execution, your bucket must have capacity for the query's requested cost, an estimate based on what you asked for.
  • After execution, the bucket is refunded the difference between the requested cost and the actual cost.
  • A single query may not exceed 1,000 points, regardless of your plan, enforced before execution based on requested cost.

That refund rule is not trivia, it changes how you write queries. Ask for first: 250 and you are charged as if all 250 come back; if only 3 exist, you are refunded. But the requested cost is what must fit in the bucket, so a greedy query can be rejected for capacity it never actually needed.

The design intent is sensible: make expensive questions cost more. It pushes you toward asking for exactly what you need, which is also the reason for GraphQL. The practical consequences are consistent: request only the fields you use, page deliberately, and expect to handle throttling as a normal condition rather than an error, because at any real volume you will meet it.

3. Webhooks and the duplicate problem

Polling an API to ask "anything new?" is wasteful and slow. Webhooks invert it: Shopify calls your endpoint when something happens. You register a URL for a topic, an order is created, and Shopify posts you the payload.

Then comes the detail that decides whether your integration is correct or quietly broken. Shopify operates an at-least-once delivery model, not exactly-once. Your application might receive the same event multiple times.

This is not a defect, it is the honest engineering trade-off any distributed system faces. If Shopify sends an event and does not hear a confirmation, it cannot know whether you processed it and the acknowledgement was lost, or you never received it. Guaranteeing exactly-once delivery across an unreliable network is famously not achievable in general, so the platform chooses the safe failure: send again. Better a duplicate than a lost order.

Which moves the burden to you. Your handler must be idempotent: processing the same event twice must produce the same result as processing it once. Otherwise a retried webhook charges a customer twice, sends two emails, or creates two records in your warehouse system.

Shopify gives you the tool: every delivery includes an X-Shopify-Webhook-Id header. Use it as your deduplication key. Record the IDs you have processed and ignore repeats.

The lesson generalizes to every webhook you will ever consume: assume duplicates and design for them. "It worked in testing" is not evidence, because duplicates arrive under load and failure, exactly when you are not watching.

4. Bulk operations

Now the other wall: "update 40,000 products." Doing that through normal queries means tens of thousands of calls, each drawing from your cost bucket, taking hours and colliding with throttling constantly. It is the wrong shape of tool.

Bulk operations, available only through the GraphQL Admin API, solve it by inverting the model. Instead of you pulling data request by request, you hand Shopify the job and let it run asynchronously:

  • bulkOperationRunQuery for reading. You submit a query, Shopify executes it in the background across the whole dataset and produces a JSONL file you download when it finishes.
  • bulkOperationRunMutation for writing. You upload a JSONL file of input data, and Shopify executes your mutation once per line, asynchronously.

The crucial property is the rate-limit treatment: your GraphQL requests to start and poll the operation count as normal API calls and are subject to rate limits, but the bulk operation's own execution is not. So the work itself runs outside your cost bucket. That is the entire point, and it is why fighting rate limits with a loop is the wrong instinct.

On concurrency, API versions 2026-01 and higher allow up to five bulk query operations running concurrently per shop, so you can process several large datasets at once rather than serializing them.

The shift in thinking is the valuable part: stop asking repeatedly and start submitting a job. Bulk work is a batch problem, and treating it as a fast loop of small requests is how integrations end up slow, throttled, and fragile.

5. A worked integration

Put the pieces together on a realistic job: sync paid orders into an external warehouse system.

1. Register a webhook   topic: orders/paid  ->  https://you/hooks

2. Shopify POSTs the order to your endpoint

3. Your handler:
     id = headers["X-Shopify-Webhook-Id"]
     if seen(id): return 200        <-- idempotency, the duplicate case
     mark_seen(id)
     enqueue(order)                 <-- do the slow work elsewhere
     return 200 quickly             <-- ack fast or be retried

4. A worker pulls from the queue and calls the warehouse API.
   Fails? Retry with backoff. The webhook is already acked.

5. Nightly reconciliation:
     bulkOperationRunQuery over yesterday's orders
     compare against the warehouse, repair gaps

Four design choices there are the whole lesson.

Deduplicate on the webhook ID, because at-least-once delivery guarantees you will see repeats.

Acknowledge fast, work later. Your endpoint's only job is to accept the event. Doing slow work inline risks a timeout, which Shopify reads as failure and retries, producing more duplicates and a worse problem.

Retry the warehouse call, not the webhook. Once acked, the event is yours to keep safe.

Reconcile in bulk. This is the step people skip and regret. Webhooks can be missed, endpoints have outages, and networks fail. A nightly bulk query that compares state and repairs gaps turns a silent divergence into a self-healing system. Event streams tell you what changed; bulk queries tell you what is true. You want both.

6. Choosing the right tool

You now have four options. Choosing well matters more than any implementation detail, because the wrong choice costs you for years.

UseWhenCost to you
Flowreact to a store event with Shopify's own datanone, visual, visible to the team
Functionschange a checkout decision as it happensa developer and an app, or install an existing one
Third-party appa common need someone already solvedsubscription, less control
API + webhooksexternal systems, custom data, bulk worksoftware you own forever

The rule, in order:

  1. Can Flow do it? Then stop. No code, no hosting, no maintenance, and legible to everyone.
  2. Is it a checkout decision? Functions. Check the app store first, tiered discounts and shipping rules are solved problems.
  3. Does an app already do it? A subscription is almost always cheaper than building and maintaining an integration.
  4. Only then, build. When the need is genuinely specific to your business and genuinely outside the platform.

The failure mode is predictable and expensive: a developer reaches for the API because it is familiar and unlimited, and the store ends up with a custom service that duplicates what Flow does natively, needs a server, and breaks when its author leaves.

The throughline of the cursus: Shopify gives you layered extension points, and the skill is picking the shallowest one that works. Flow reacts, Functions decide, APIs integrate. Reaching deeper than necessary is not sophistication, it is unnecessary permanent cost, and the store still has to run at 3am on Black Friday.

7. Choosing an automation surface

Work down the layers: use Flow if a store event and Shopify's own data suffice, Functions if a checkout decision must change as it happens, an existing app if the need is common, and only build an API integration when the need is genuinely external, custom, or bulk.

flowchart TD
  A["I want to automate something"] --> B{"React to a store event?"}
  B -->|Yes| C["Use Flow: no code"]
  B -->|No| D{"Change a checkout decision?"}
  D -->|Yes| E{"Does an app already do it?"}
  E -->|Yes| F["Install the app"]
  E -->|No| G["Write a Function: WebAssembly in an app"]
  D -->|No| H{"External system or bulk work?"}
  H -->|Yes| I["API, webhooks, bulk operations"]
  I --> J["Software you own: idempotency, retries, reconciliation"]

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. How does Shopify's Admin GraphQL API rate limiting work?
    • A fixed number of requests per minute regardless of what they ask for
    • A calculated query cost on a leaky bucket: cost scales with what you request, the bucket must hold the requested cost before execution, and is refunded the difference from the actual cost
    • Unlimited requests on paid plans
    • One request per second per store
  2. Why must Shopify webhook handlers be idempotent?
    • Because Shopify sends webhooks only once and you must not miss them
    • Because webhooks arrive out of order
    • Because Shopify uses at-least-once delivery, not exactly-once, so the same event may arrive multiple times
    • Because idempotency improves rate limits
  3. What makes bulk operations the right tool for updating 40,000 products?
    • They run faster because they skip validation
    • You hand Shopify an asynchronous job (via JSONL) and the bulk operation's own execution is not subject to rate limits, unlike a loop of tens of thousands of throttled calls
    • They are the only way to write data at all
    • They bypass the need for an API key
  4. In the worked integration, why acknowledge the webhook quickly and do the work in a queue?
    • To reduce the GraphQL query cost
    • Because slow inline work risks a timeout, which Shopify reads as failure and retries, producing more duplicates and a worse problem
    • Because queues are required by Shopify
    • To avoid needing idempotency
  5. What is the correct order for choosing an automation approach?
    • Always build a custom API integration for maximum control
    • Flow first if it fits; then Functions for checkout decisions (checking the app store first); then an existing app; and only build an API integration for genuinely external, custom, or bulk needs
    • Start with the API, then simplify later
    • Use all four surfaces together for redundancy

Related lessons

Business
intermediate

Shopify Functions: Custom Logic Inside Checkout

Flow reacts after events; Functions participate during them. This lesson covers how Shopify Functions let you change discounts, shipping, payment options, and cart validation from inside checkout, why they run as WebAssembly compiled from JavaScript or Rust, and why that architecture replaced the old sandboxed Scripts.

8 steps·~12 min
Business
intermediate

Automating a Store with Shopify Flow

Most store work is repetitive reaction: tag this order, email that customer, reorder when stock drops. Shopify Flow turns that into automation with a trigger-condition-action model. This lesson covers how Flow works, over 100 event triggers, worked workflows, and the important part, where Flow stops and you need something else.

7 steps·~11 min
Programming
beginner

When It Outgrows the Tool, and Who Owns It Meanwhile

Automations become infrastructure without anyone deciding they should. This lesson covers shadow automation and why banning it fails, documenting a flow so it survives its author, the signals that a workflow has outgrown no-code, and how to migrate without a rewrite.

8 steps·~12 min
Programming
beginner

Building Flows That Survive Contact With Reality

The concrete patterns that separate an automation that works from one that keeps working: validating input at the boundary, retrying only what is safe to retry, handling rate limits and batches, testing something you cannot easily test, and keeping secrets out of the flow.

8 steps·~12 min