Acumatica · Azure

Azure Service Bus for ERP

Azure Service Bus for ERP is the work that turns a collection of business systems into a coherent operation. ERP, CRM, e-commerce, payment, marketing, support — each is a tool.

John Kihiu12 min read

ERP integrations fail most often not because the individual API calls are wrong, but because the integration assumes both systems are always available and processes messages synchronously. Azure Service Bus decouples that assumption: a message queued for the ERP survives a downstream outage, retries on failure without losing data, and lets producers and consumers scale and deploy independently of each other. For Acumatica and similar ERPs, where a nightly batch job or a third-party system integration can easily overwhelm a synchronous API, this is the difference between a brittle integration and one that survives a bad afternoon.

Queues vs. topics: picking the right entity

A Service Bus queue is a simple point-to-point channel: one producer, one logical consumer (or a competing set of consumers pulling from the same queue), useful for a single well-defined workflow like "sync new sales orders into the ERP." A topic adds publish/subscribe fan-out — one message published to a topic can be delivered to multiple independent subscriptions, each with its own filter, which is the right shape when an ERP event (say, an invoice posted) needs to notify several unrelated downstream systems without the publisher knowing who's listening.

C# · SENDING A MESSAGE
await using var client = new ServiceBusClient(connectionString);
var sender = client.CreateSender("erp-sales-orders");

var message = new ServiceBusMessage(JsonSerializer.SerializeToUtf8Bytes(order))
{
    MessageId = order.ExternalOrderId,   // enables duplicate detection
    ContentType = "application/json",
};
message.ApplicationProperties["source"] = "ecommerce-platform";

await sender.SendMessageAsync(message);

Idempotent consumers and duplicate detection

Service Bus guarantees at-least-once delivery by default, which means a consumer must tolerate receiving the same message more than once — a network blip between message processing and the completion acknowledgment is enough to cause a redelivery. Set MessageId to a stable business key (an order number, not a random GUID) and enable duplicate detection on the queue so Service Bus itself discards exact re-sends within the detection window; on the consumer side, check whether the referenced ERP record already exists before creating it, since duplicate detection alone doesn't cover every retry scenario.

Dead-letter queues for poison messages

Every queue and subscription in Service Bus has a built-in dead-letter sub-queue that messages land in automatically after exceeding MaxDeliveryCount retries or when a consumer explicitly dead-letters a message it can't process. A malformed order payload or an ERP validation failure shouldn't retry forever and block the queue behind it — dead-letter it with a reason code, alert on dead-letter queue depth, and give someone a runbook for inspecting and replaying those messages once the root cause is fixed.

Set MaxDeliveryCount deliberately, not to the default

The default MaxDeliveryCount (10) is often too high for ERP integrations where a validation failure is a data problem, not a transient one — retrying a message that will never succeed 10 times just delays detection. For known-permanent failures (bad account codes, missing required fields), fail fast and dead-letter after 2-3 attempts; reserve higher retry counts for genuinely transient issues like a downstream API timeout.

Sessions for ordered processing

Standard Service Bus queues don't guarantee message order once you have multiple concurrent consumers. If the ERP integration needs strict ordering — line items for the same order must be processed in sequence, for example — enable sessions and set SessionId to a value shared by all messages that must stay in order (the order ID). Service Bus then guarantees a single consumer processes all messages for a given session in the order they were sent, while still allowing different sessions to process in parallel.

Sessions add latency for the ordering guarantee

A session-enabled receiver locks an entire session, not just one message, so an unusually slow message in a session blocks the rest of that session's queue. Only enable sessions where ordering genuinely matters — most ERP sync scenarios (independent order creation, independent inventory updates) don't need it and are faster without it.

Scaling and cost considerations

The Standard tier bills per operation and is the right starting point for most ERP integration volumes; the Premium tier moves to dedicated, predictable-latency capacity billed per messaging unit, worth it once throughput or latency-sensitivity justifies the fixed cost. Auto-forwarding lets a queue automatically push processed messages to another queue or topic, which is a useful pattern for chaining an ERP sync pipeline (validate → transform → post) without custom orchestration code for the handoff between stages.

FeatureUse for
QueueSingle consumer/consumer group, point-to-point
Topic + subscriptionsFan-out to multiple independent consumers
Duplicate detectionAt-least-once delivery safety net
Dead-letter queueIsolating permanently failing messages
SessionsStrict per-entity ordering when required

Wrapping up

Azure Service Bus earns its place in an ERP integration once synchronous, direct API calls between systems start failing under load or during the ERP's own downtime — queuing decouples the two systems' availability from each other. Design consumers to be idempotent from day one, set dead-letter thresholds based on whether a failure is transient or permanent, and reach for sessions only when ordering is a genuine business requirement rather than a default choice.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.