How Shopify and Bizmitra sync

There are exactly two ways data reaches Bizmitra from your store: a one-off import when you connect it, and webhooks for everything after that. This guide covers both, what happens to each change once it arrives, and how repeated or self-inflicted events are discarded.

Last reviewed against the implementation on

At a glance

Two mechanisms
A paged import at connect time, then webhooks
Not a schedule
Nothing polls your store on a timer
Unit of work
One sync job per entity, with a visible state
Repeat safety
Redelivered and self-echoed events are dropped

The initial import

When you authorise the app, Bizmitra does a one-off backfill: it pulls your existing products, customers and orders out of the Shopify API page by page — 250 records at a time — and queues each record as its own piece of work.

That backfill runs on a separate queue from live events. A store with 40,000 historical orders would otherwise monopolise the same workers that need to handle the order somebody places while the import is still running. Bulk imports go to the bulk queue; webhooks stay on the live one, and neither starves the other.

Worth knowing

The import is cursor-paged, not date-filtered. Orders are pulled with status any, so cancelled and archived orders come across too rather than being silently skipped.

Ongoing webhooks

After the backfill, nothing polls your store. Bizmitra registers webhooks on the store when you connect it, and Shopify notifies Bizmitra when something changes. The topics registered are:

Shopify webhook topics Bizmitra subscribes to.
TopicWhat Bizmitra does with it
products/create, products/update, products/delete Creates, updates or removes the matching stock item.
customers/create, customers/update Creates or updates the customer and its party ledger.
orders/create, orders/updated, orders/cancelled Writes or updates the sales order.
orders/paid Treated as money-in — raises the invoice and/or receipt, not another order.
refunds/create Posts a credit note against the invoice for that order.
app/uninstalled Marks the connection as gone so nothing keeps trying to sync.
app_subscriptions/update Tracks the billing charge being approved, declined, frozen or cancelled.

Every incoming webhook is checked against its Shopify signature before it is processed. A request that does not carry a valid signature is rejected, so an event cannot be forged by anyone who happens to know your callback URL.

Webhook bodies are logged as received, which for customer and order topics means they contain customer information the integration itself never reads. Protected customer data sets out exactly what is retained where, and what the privacy webhooks delete.

Not real-time, and we will not call it that

Webhooks arrive as events happen, which is much faster than a scheduled poll — but delivery is Shopify's to guarantee, and each event still queues behind whatever is already being processed. Treat it as prompt, not instantaneous.

What a sync job is

Whichever way a change arrives, it becomes one sync job: a stored record of one entity, one direction and one attempt to apply it. That is the unit you can look at when you want to know what happened to a specific order, and it is why "did it sync?" has a real answer rather than an educated guess.

Event

A webhook arrives, or the import yields a record

Queued

A sync job is written with the normalised entity attached

Applied

A handler writes the ERP document and records the mapping

Job states

A job is always in exactly one of five states:

StateMeaning
pendingQueued, waiting for a worker.
processingBeing written into the ERP right now.
successApplied. The document exists and the mapping is recorded.
failedDid not apply this attempt, and is eligible to be retried.
deadRetries exhausted. It stops here and waits for a person — it is not discarded.

The distinction between failed and dead matters. A failure is usually transient — a rate limit, a timeout — and resolves itself on retry. dead means retrying will not help, and something needs a decision. Dead jobs remain replayable once the underlying cause is fixed; the classic case is a refund that arrived before its invoice existed.

Duplicates and echoes

Two different problems, both handled, and both worth understanding because they are where naive integrations produce doubled invoices.

Shopify redelivering an event

Shopify will resend a webhook it is not sure you received. Before anything is posted, Bizmitra resolves the entity against the mapping it already holds — so a repeated orders/paid finds the invoice that already exists and returns it, rather than raising a second one. This is handled in the invoice conversion itself rather than at each call site, which is why it holds no matter which order-flow mode raised the invoice.

Bizmitra's own writes bouncing back

When Bizmitra pushes a product to Shopify, that write causes Shopify to fire products/update — describing a change Bizmitra just made. Importing it would be a loop. So each successful export stamps the mapping with the version Shopify returned; a webhook arriving shortly afterwards carrying that same version is recognised as an echo and dropped. A genuine later edit carries a new version and is processed normally.

What syncs, which way

The full surface. Anything not in this table does not sync, in either direction.

Data Shopify → Bizmitra Bizmitra → Shopify
Products Yes Yes
Customers Yes Yes
Orders Yes No
Payments Yes No
Refunds Yes No
Stock levels No Yes

The asymmetries are deliberate. Shopify is the system of record for orders and payments, so Bizmitra reflects them and never writes an order back. Stock is the mirror image: Bizmitra holds the position across all your warehouses, so it flows outward only. Two systems both claiming to own the same fact is how data gets corrupted.

One thing that looks supported but is not

Shopify can notify Bizmitra of storefront stock changes, and that topic does appear in the app's manifest — but there is no inbound handler for it, so those notifications are not acted on. Stock changed in Shopify does not flow back into Bizmitra. See the inventory guide.

Connection health

A health check asks your store a deliberately cheap question using this connection's own credentials. It distinguishes between problems that look identical from the outside:

  • Authorisation rejected — the store no longer accepts the token; reconnect it.
  • Signed in but not permitted — the app is connected but has not been granted the access it needs.
  • Store unreachable — nothing answered at all, which is a network or address problem, not a permissions one.
  • Rate limited — the store is throttling; this usually clears on its own.

A scheduled check runs across connections and raises an alert when one stops answering, so a store that quietly disconnects is noticed rather than discovered a fortnight later during reconciliation.

Bizmitra Assistant