Idempotent writes with stored-response replay
Order pre-alerts carry an external order ID; retrying the same creation returns the original result. This is what makes network failures boring instead of expensive.
Guide · Developers
A production fulfillment integration needs five properties: idempotent writes with stored-response replay, HMAC-signed webhooks carrying immutable event IDs, estimates and packed quotes modelled as separate objects, inventory read from an immutable movement ledger, and tracking normalized across the China leg and the international carrier. Miss one and the failure mode is a physical parcel, not a database row.
Integrating China-leg fulfillment into a storefront, OMS or agent runtime is not a CRUD exercise: a retried request can ship a duplicate parcel, an unverified webhook can corrupt order state, and an estimate mistaken for a bill breaks trust with real money. Merchant API V1 (45 operations, 12 webhook events, 22 MCP tools) is the worked example throughout.
Order pre-alerts carry an external order ID; retrying the same creation returns the original result. This is what makes network failures boring instead of expensive.
Webhooks are HMAC-SHA256-signed over the raw body, carry immutable event IDs, and are delivered at-least-once. Verify, dedupe, then process — in that order.
The API never lets a pre-measurement estimate masquerade as a bill. The packed quote is produced after measurement and is what settlement reconciles against, line by line.
Stock is a fold over immutable movements, not a mutable number. Your reconciliation job can replay the ledger and must land on the same statement the platform produces.
China-leg events and international carrier events merge into one timeline; when a carrier reissues a tracking number mid-transit, it is re-registered instead of going dark.
Diff your own implementation against the contract behaviour for each failure. Every one of them happens in production rather than in theory, and each has exactly one correct response.
| Scenario | What a naive integration does | What the contract does instead |
|---|---|---|
| Order creation times out and you retry | Creates a second order. A duplicate physical shipment goes to a real customer, and you find out from the settlement. | The retry carries the same Idempotency-Key and external order id, and replays the stored response. A conflicting body with a used key is refused outright rather than silently accepted. |
| Two workers retry the same request concurrently | Both proceed. Whichever wins, the loser’s result is lost or duplicated. | A matching idempotent request that still owns the processing lease is refused with a distinct code, so the caller retries after a short delay with the same key and body. |
| A webhook is delivered twice | Order state is written twice. Anything non-idempotent downstream — a customer email, a ledger entry — fires twice. | Delivery is at-least-once by design and every event carries an immutable event ID. Verify the signature, deduplicate on the ID, then process — in that order. |
| A webhook body is tampered with in transit | Parsed and trusted, because the JSON looked fine. | HMAC-SHA256 is computed over the raw request body and compared before any parsing. Mismatches are rejected, not logged and processed. |
| An estimate is stored as the price | The customer is charged a number produced before the goods were measured, and the settlement disagrees with it. | Estimate and packed quote are distinct objects. The packed quote is produced after measurement and is what the merchant approves; settlement reconciles against it line by line. |
| Inventory is cached as a counter | The counter and reality drift, and nothing in the system can tell you when they diverged. | Stock is a fold over immutable movements. Your reconciliation job can replay the ledger and must land on the same statement the platform produces. |
| A carrier reissues a tracking number mid-transit | The shipment goes dark and support gets a ticket. | The new number is re-registered against the same order, and China-leg plus carrier events stay in one merged timeline. |
| The wallet balance is short when work is created | The request fails and your queue treats it as a permanent error. | A distinct payment-required response names an insufficient available balance. The order is held pending top-up rather than rejected, and shipments already in flight are never held. |
Delivery is at-least-once and every event carries an immutable event ID. Verify the HMAC over the raw body, deduplicate on the event ID, then act. The receiver behaviour set out for each event assumes the first two steps have already happened.
| Event | What it means | What your receiver should do |
|---|---|---|
parcel.received | A domestic supplier parcel was scanned and recorded at the warehouse. | Mark the supplier parcel as arrived and surface the intake evidence to the merchant. Safe to receive more than once per parcel. |
inspection.completed | An inspection you requested finished. | Attach the inspection outcome to the order. Do not treat it as permission to dispatch. |
quote.ready | Packing and measurement produced the final packed quote; the order moved to payment. | The binding number now exists. Stop using any stored estimate for this order and route the quote to whoever approves it. |
payment.completed | The shipment settlement was paid; the order moved on to dispatch. | Settlement is paid. Reconcile the line items against the packed quote before writing the order to your ledger. |
shipment.dispatched | Carrier events confirm the shipment entered the provider network. | Push tracking to your storefront. Expect the tracking number to be re-registered if the carrier reissues it. |
tracking.updated | A normalized WooliiPorter parcel tracking fact changed. The payload contains public shipment and parcel identifiers only. | Reserved for future granular pushes — poll the tracking endpoint today rather than depending on this event. |
shipment.exception | An exception was recorded against the shipment. | Raise it to a human. This is the event most worth alerting on rather than logging. |
shipment.delivered | Every outbound package on the order has a delivered carrier fact. | Every outbound package on the order has a delivered carrier fact. Safe to close the order. |
inbound.exception.opened | WooliiPorter opened an inbound receiving review. The payload contains the IB number and customer-safe exception projection only. | Verify, deduplicate on the event ID, then apply idempotently. |
inbound.exception.updated | The public state, customer-action requirement or resolution of an inbound receiving review changed. | Verify, deduplicate on the event ID, then apply idempotently. |
cancellation.updated | The interception state of a cancellation request changed — warehouse stop, stock return, review, final cancellation or rejection. The payload mirrors the cancellation object returned by GET fulfillment intent; once dispatched, orders can no longer be cancelled and the request resolves through review instead. | Verify, deduplicate on the event ID, then apply idempotently. |
account.balance_low | The merchant wallet is below the operating threshold. This account-level event has no order identity. | Verify, deduplicate on the event ID, then apply idempotently. |
An endpoint is created with the event types it wants and returns a signing secret once — it is never returned again. Disabling an endpoint keeps its delivery ledger, because the ledger is evidence. Accounts hold up to five active endpoints. Full parameter and signature detail lives in the Merchant API reference.
Full reference — every operation, parameter, error shape and webhook event — lives in the Merchant API documentation. Store-side WooCommerce integration is covered separately on the plugin page.
Because the failure mode is a duplicate physical shipment, not a duplicate database row. A timeout on order creation must be safely retryable: with stored-response replay, the retry returns the original response instead of creating a second order that ships real goods to a real customer.
Compute an HMAC-SHA256 over the raw request body with your endpoint secret and compare it to the signature header — before parsing the JSON. Combine that with immutable event IDs and at-least-once delivery semantics: your receiver deduplicates on the event ID, so replays and retries are harmless.
An estimate is computed before goods are measured; the packed quote is generated after packing from real dimensions and weight against the live rate table, and is the number the merchant approves before dispatch. A correct integration never presents an estimate as a final price — the API keeps them as distinct objects.
Read inventory that is computed from an immutable stock-movement ledger rather than a mutable counter — the same ledger that produces the monthly statement. Every receive, reserve, dispatch, withdrawal and adjustment is a movement; your integration can re-derive state at any point in time.
Account-first and approval-gated: create a merchant login and submit the integration application in-account. Approval creates and activates the merchant account, then opens sandbox and production key management. There is no public unauthenticated API, and no agent or third party can issue keys on your behalf.
Three classes, because they need three different behaviours. Retry-with-backoff: rate limiting and the in-progress idempotency lease. Retry-after-action: an insufficient wallet balance, which needs a top-up rather than a retry loop. Do-not-retry: validation failures, scope and auth problems, and state conflicts such as paying before the packed quote exists. Treating all non-2xx responses identically is how a queue melts down on a 402.
Creating new warehouse work returns a distinct payment-required response naming the shortfall, and the order is held pending top-up rather than rejected. Shipments already in flight are never held for a balance question. Your integration should surface this to the merchant as an action rather than retrying it as a transient failure.
Yes — a standalone MCP server exposes the supported subset as typed tools for AI agents (estimates, idempotent orders, value-added services, quotes, tracking, settlement, stocking, fulfillment). Same authentication model, same human-approval gates on packed quotes and mixed orders.