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:
| Topic | What 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.
A webhook arrives, or the import yields a record
A sync job is written with the normalised entity attached
A handler writes the ERP document and records the mapping
Job states
A job is always in exactly one of five states:
| State | Meaning |
|---|---|
pending | Queued, waiting for a worker. |
processing | Being written into the ERP right now. |
success | Applied. The document exists and the mapping is recorded. |
failed | Did not apply this attempt, and is eligible to be retried. |
dead | Retries 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.