跳至主要内容

Storefronts and marketplaces

Put the unit back once, not twice

A refund is issued, and the unit is supposed to become sellable again. The restock itself is one call. The difficulty is that it must happen exactly once: a retried job that adds the unit back a second time creates stock which does not exist, and subtracting one afterwards is a correction, not an undo. Shopify reached the same conclusion about its own inventory mutation and made the idempotency key mandatory.

Triggers
Two
Runs
Every 10 minutes
Stack
5 tools
Durable execution
Either one

The flow

The top track answers the customer. The bottom track moves stock on a timer, behind a check that the same refund has not already been restocked.

Platform

A refund is created

Shopify Admin API

Data

Claim the refund id

Sheets · Airtable

Notify

Tell the customer

Slack · WhatsApp

Orchestration

Every 10 minutes

n8n schedule

Orchestration

List unrestocked claims

n8n

Orchestration

Restock not yet applied?

Temporal · Inngest

Platform

Adjust stock with a key

Shopify Admin API

Orchestration

Do nothing

Step by step

  1. 01

    A refund is created

    Shopify Admin API

    A refund webhook fires. It carries the refund id and the restock behaviour chosen when the refund was made. Read both, and write neither back yet.

  2. 02

    Claim the refund id

    Sheets · Airtable

    One row per refund id, unique on that id. Writing the row is the claim; if it already exists, this refund has been seen and nothing further is owed. This table is the idempotency record, and it is cheap because it belongs to you.

  3. 03

    Tell the customer

    Slack · WhatsApp

    Confirm the refund to the customer, and tell whoever handles returns. This message is about money returned, not about stock — two different facts, and they do not have to arrive together.

  4. 04

    Every 10 minutes

    n8n schedule

    The restock runs on the timer rather than on the webhook, so a refund that arrived while the flow was down is still picked up on the next pass.

  5. 05

    List unrestocked claims

    n8n

    Read claims with no restock confirmation. A claim written but never confirmed is precisely the crash case, which is why the claim and the confirmation are separate columns rather than one flag.

  6. 06

    Restock not yet applied?

    Temporal · Inngest

    Confirm the restock has not run, then run it, inside one execution that can be retried whole. Neither n8n nor Make remembers that the adjustment already went through, so a retry after a lost response adds the unit a second time, and the stock figure has no way to show which unit was the phantom. Either durable execution, or an idempotency key the platform enforces.

  7. 07

    Adjust stock with a key

    Shopify Admin API

    This applies only to refunds recorded as no-restock, or issued through the return refund mutation, which takes no restocking input. A refund created with cancel or return has already been restocked by Shopify, and adding a unit here would create the phantom stock this case exists to prevent. Adjust with a key derived from the refund id, so a retry cannot add a second.

What the platform will not allow

The restock is one call, but what that call means, and how often it may safely be made, is set by Shopify.

  • refundCreate "creates a refund for an order, allowing you to process returns and issue payments back to customers", and it is the mutation that lets you specify restocking behaviour for line items — where returnRefund "focuses solely on handling the financial refund without any restocking input". Restock is decided at refund time, not bolted on afterwards.

    Shopify Admin GraphQL API — refundCreate
  • The restock behaviour is an enum, not a yes or no. CANCEL says "use this when restocking unfulfilled line items"; RETURN says "use this when restocking line items that were fulfilled"; NO_RESTOCK records that the line "was not restocked". The older LEGACY_RESTOCK is deprecated and "is not accepted when creating new refunds". Choose the wrong one and the unit comes back from the wrong state, or does not come back.

    Shopify Admin GraphQL API — RefundLineItemRestockType
  • inventoryAdjustQuantities applies a delta, not an absolute quantity. Shopify's own note: "as of version 2026-01, this mutation supports an optional idempotency key using the @idempotent directive", and "as of version 2026-04, the idempotency key is required and must be provided using the @idempotent directive". The platform moved that key from optional to mandatory for the same reason this flow exists — a delta applied twice cannot be undone by applying it again.

    Shopify Admin GraphQL API — inventoryAdjustQuantities
  • A WhatsApp customer service window opens when the user messages or calls you, and resets if they do so again before it expires; "when the window closes, you can only send pre-approved template messages". A refund confirmation sent days after the customer last wrote must therefore be a template, not free text.

    WhatsApp Cloud API — Send messages

When this is not worth building

Three cases where the upkeep costs more than the phantom stock.

  • You do not restock refunds at all — the returned unit is written off, sent for inspection, or resold through another channel. Then the refund and the stock level are separate stories, and forcing them into one flow invents a link your operation does not have.

  • Refunds are rare enough that one person handles each by hand and remembers doing it. The whole flow exists to guarantee once; someone touching five refunds a week already guarantees that, and more cheaply.

  • You have nowhere to record that a restock happened. Without a claim row every part of this is guesswork, because the flow cannot tell a refund it has not processed from one it processed and forgot.

Related reading

联络我们

这些流程,你真正应该做哪一条?

我们每星期都在为香港的运营团队跑这些流程。告诉我们哪个环节正在耗你的时间,我们会告诉你自动化划不划得来。

开始对谈