Use webhooks or short-interval polling to learn quickly that something changed in the ERP or the store, put each change on a queue, process it idempotently, and run a scheduled reconciliation job to catch whatever the real-time path missed. No single mechanism is reliable alone: polling is simple but slow and wasteful, webhooks are fast but not guaranteed, and queues add resilience but not discovery. This article compares the three and uses Business Central webhooks as a concrete example.
Who this is for
This is for developers and integration architects connecting an ERP such as Microsoft Dynamics 365 Business Central to an online store or marketplace for products, stock, prices, customers and orders. By the end you will know which sync mechanism fits each data type, what to build around it, and which failure modes to test.
What typically syncs, and in which direction
| Data | Usual master | Direction | Tolerance for delay |
|---|---|---|---|
| Products, descriptions, categories | ERP or PIM | ERP to store | Minutes to hours |
| Prices and discounts | ERP | ERP to store | Minutes |
| Stock levels | ERP | ERP to store | Seconds to minutes; oversell risk |
| Customers | Often both | Two-way | Minutes |
| Orders | Store | Store to ERP | Seconds to minutes |
| Shipments and invoices | ERP | ERP to store | Minutes |
Name one system of record for every field. Two-way sync without a clear master per field produces overwrite loops. Where both sides can edit, decide a rule, such as "latest modified timestamp wins" or "ERP wins for price".
Pattern 1: Polling
The integration asks the source for changes on a schedule, typically with a filter on a modified timestamp.
GET /api/v2.0/companies({companyId})/items?$filter=lastModifiedDateTime gt 2026-10-25T08:00:00ZThe filter above is illustrative; check which fields and filters your ERP's API supports.
- Strengths: simple, works with any source that has an API, easy to reason about and to replay.
- Weaknesses: latency equal to the polling interval, wasted calls when nothing changed, and exposure to API rate limits. See Microsoft Graph throttling and Dataverse service protection limits for how throttling behaves on Microsoft platforms.
- Key technique: persist a high-water mark (the last modified timestamp processed), and overlap each window slightly so that records committed late are not missed. The overlap will return some records twice, so processing must be idempotent.
Pattern 2: Webhooks
The source calls your endpoint when data changes. Business Central documents this clearly, so it makes a good example.
How Business Central webhooks work
- Register a subscription with
POST /api/v2.0/subscriptions, passingnotificationUrl, theresourceto watch and an optionalclientState. - Handshake. Business Central calls your
notificationUrlwith avalidationTokenquery parameter. You must return that token in the response body with status200. The handshake is mandatory when creating and when renewing a subscription. clientStateis returned in each notification, so you can use it as a shared secret to verify the sender.- Expiry. Subscriptions expire after three days unless renewed with a
PATCHrequest, which repeats the handshake. TheexpirationDateTimeproperty shows when. - Payload. A notification lists
subscriptionId,clientState,expirationDateTime,resource,changeTypeandlastModifiedDateTime. The change type iscreated,updated,deletedorcollection. There is no record data in the payload, so you call the API to read the current state. - Batching and delay. Business Central does not notify immediately. By default it waits 30 seconds after the first change to an entity, and if more than 1,000 records change during that delay, it sends a single
collectionnotification whose resource includes a filter you use to fetch all matching entities. Otherwise it sends one notification per change. - Retries. If Business Central can't reach the subscriber, it retries over the next 36 hours, but only if you answer with
408,429or a5xxstatus. Any other response code means no retries and the subscription is deleted.
A minimal registration looks like this:
POST https://{businesscentralPrefix}/api/v2.0/subscriptions
Content-type: application/json
{
"notificationUrl": "https://integration.contoso.com/hooks/bc",
"resource": "/api/v2.0/companies(f64eba74-dacd-4854-a584-1834f68cfc3a)/items",
"clientState": "a-long-random-secret"
}The details of subscriptions, including supported entities and restrictions, are in Business Central API webhooks and subscriptions.
Webhook strengths and weaknesses
- Strengths: low latency, no wasted calls.
- Weaknesses: delivery is not guaranteed, receivers must be available and fast, subscriptions can lapse, and some entities are not supported. For example, Business Central webhooks are not available for APIs on temporary tables, system tables or API-type queries. Notifications for changes made by users who cannot schedule job queues may be delayed until another change by an authorised user.
Shopify's webhook guidance, as another example from the store side, says delivery isn't guaranteed, so apps shouldn't rely solely on webhooks and should run reconciliation jobs that periodically fetch data using updated_at filters. It also says to verify HMAC signatures, ignore duplicates using the X-Shopify-Webhook-Id header, and notes that ordering is not guaranteed within or across topics for the same resource, so use the X-Shopify-Triggered-At header or the payload's updated_at field to sequence events.
Pattern 3: Event queues
A queue is not a way to discover changes; it is how you absorb and process them safely. Put the receiving endpoint in front of a queue such as Azure Service Bus:
ERP webhook / poller --> receiver (validate, enqueue, return 200) --> queue --> workers --> store API
|
+--> dead-letter queue- The receiver does only three things: validate the sender (token handshake,
clientStateor signature), enqueue a message and return a success status quickly. This protects subscriptions, because a slow or failing receiver can cause retries or, in the case of Business Central, a deleted subscription. - Workers read the queue, call the ERP API for the current record, transform it and call the store API.
- Failures retry, and messages that keep failing go to the dead-letter queue for investigation.
Service Bus peek-lock delivery is at least once, so duplicates are possible; set MessageId from a stable key and make workers idempotent. The mechanics of queue-based buffering are covered in Queue-Based Load Leveling with Azure Service Bus, and receiver design in Webhooks that don't double-charge.
Choose a pattern
| Requirement | Recommended approach |
|---|---|
| Source has no webhooks | Poll with a high-water mark, plus a queue for processing |
| Updates must appear in seconds | Webhooks into a queue, plus periodic reconciliation |
| Bulk imports of thousands of records | Batch pull on a schedule; do not trigger a webhook storm |
| Store API rate limits are tight | Queue with a bounded number of workers |
| Compliance requires an audit trail | Persist every message and every processing outcome |
The strongest default is a hybrid: webhooks for speed, a queue for resilience and a scheduled reconciliation job for correctness.
Build the reconciliation job
Reconciliation is the part teams skip and later regret. A simple version:
- On a schedule, such as hourly for stock and nightly for everything else, fetch records modified since a safe point in the past from the ERP.
- Compare them with the store, using a hash of the synced fields or the modified timestamp.
- For each difference, enqueue the same message the webhook path would have created.
- Report counts of differences found. A rising count signals that real-time delivery is failing.
Because reconciliation reuses the normal processing path, it needs no special logic and exercises the idempotency you already built.
Handle ordering and duplicates
- Duplicates: deduplicate on a stable key such as
entityId + lastModifiedDateTime, or the sender's event ID where one exists. - Out of order: do not blindly apply the payload. Fetch the current record, and skip the update when the target already holds data with an equal or newer modified timestamp.
- Deletes: decide whether a
deletedchange maps to a delete, an unpublish or an archive in the store. Deleting orders or customers in a store can break history. - Partial failures: if a product update succeeds but its stock update fails, retry the failed part rather than repeating the whole record.
Troubleshooting
Notifications stopped arriving. Check whether the Business Central subscription expired after three days, or was deleted after your endpoint returned a status outside 408, 429 and 5xx. Recreate it and add a renewal job.
Subscription creation fails. Verify that your endpoint answers the validationToken request with status 200 and the token in the body, within a short time.
Stock is wrong in the store. Compare the modified timestamps on both sides and run reconciliation. If webhooks batch into collection notifications, make sure you handle that change type by fetching with the supplied filter.
Duplicate orders in the ERP. Store orders must carry a unique external reference, and the worker must check it before creating an order. See Idempotent API Design.
The queue backs up. The store or ERP API is throttling. Reduce worker concurrency and honour Retry-After headers.
Checklist
- One system of record per field.
- Webhook or poller feeds a queue; the receiver returns quickly.
- Business Central subscriptions renewed before the three-day expiry, with alerts on failure.
- Workers idempotent, keyed on a stable identifier.
- Modified-timestamp comparison to handle out-of-order events.
- Scheduled reconciliation job reusing the normal path.
- Dead-letter queue monitored.
References
- Microsoft Learn, Business Central: Working with webhooks: https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/api-reference/v2.0/dynamics-subscriptions
- Shopify developer documentation, Webhook best practices: https://shopify.dev/docs/apps/build/webhooks/best-practices
- Microsoft Learn, Service Bus queues, topics and subscriptions: https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-queues-topics-subscriptions
- Microsoft Learn, Queue-Based Load Leveling pattern: https://learn.microsoft.com/en-us/azure/architecture/patterns/queue-based-load-leveling
Comments
Questions and tips are welcome. Comments are checked by the EpiFive team before they appear; please do not share passwords, phone numbers or other private details.