Why Procurement Software Lives or Dies by Its Real-Time Layer (and What We Built on Azure to Get There)

Procurement is a waiting game — until suddenly it isn’t. A buyer sends out an RFQ to a dozen suppliers, then checks their inbox obsessively for the next three days. A supplier submits a quote at 11pm their time, hoping someone on the buyer’s side notices before the deadline passes. That gap — the silence between “I sent something” and “someone saw it” — is where procurement platforms quietly lose their users. Not to a competitor’s better UI, but to email and spreadsheets, because at least those don’t pretend to be instant.

If you’re building a SaaS product where the core value proposition is coordinating multiple parties (buyers, suppliers, internal approvers) around a document that changes state — an RFQ, a quotation, a negotiation — then “real time” isn’t a nice-to-have feature. It’s the product. A procurement tool that requires a page refresh to know a supplier just responded is, functionally, just a slower spreadsheet. We built enquirica.com around that premise, and it shaped almost every infrastructure decision we made — starting with how we push state changes to the browser.

The problem with “just poll it”

Early on, the tempting shortcut is polling: hit an endpoint every few seconds, diff the response, update the UI. It works for a demo. It falls apart the moment you have real usage, because procurement events don’t arrive on a schedule — a supplier could submit a quote seconds after opening the RFQ or three days later. Polling means either wasteful constant traffic or laggy, unsatisfying updates, and neither scales cleanly across companies with dozens of concurrent enquiries.

So we needed a push model — server decides when something matters, client finds out immediately. On Azure, that meant **Azure Web PubSub**.

What Web PubSub actually does for us

Azure Web PubSub is a managed WebSocket service: instead of our API holding open thousands of long-lived socket connections itself (and having to think about connection scaling, sticky sessions, and reconnect storms), Azure holds the connections and we just publish messages into it. Our API talks to it through `@azure/web-pubsub` on the server and `@azure/web-pubsub-client` in the browser.

The model we landed on is deliberately simple: **groups keyed by company ID**. When a user logs in, the frontend calls a `/subscribe` endpoint. The API doesn’t hand back a generic connection string — it asks Web PubSub for a scoped client access token, tied to that user and joined to a group named after their company:

“`ts

const url = await subscribeToCompanyNotificationHub(

getWebPubSubClient(),

companyId,

userId

);

“`

That token grants exactly two roles — join/leave the group, send to the group — nothing broader. The browser opens a `WebPubSubClient` against that URL and listens for `group-message` events. This is the boring, correct way to do multi-tenant real time: authorization happens at token-issuance time on the server, not by trusting the client to only subscribe to “its own” channel.

On the publish side, when a supplier submits a quotation, the API doesn’t wait for anything client-side to poll — it fires a notification straight into the RFQ owner’s company group:

“`ts

broadcastNotification<PubSubQuotationNotification>(

client,

quotation.enquiry.owner.id.toString(),

‘quotationSubmission’,

{ enquirySlug, quotationId, supplier, total }

);

“`

The buyer sees a live notification — supplier name, quoted total, a direct link into the comparison view — within roughly a second of submission, with zero polling and zero page refresh. The same group-based broadcast pattern covers new conversation messages between buyers and suppliers, so a negotiation thread feels like a chat app, not an email chain with extra steps.

Why not just SSE?

We actually run both, deliberately. Server-Sent Events (a plain `EventSource` connection held open by our own Express API) work well for simpler, one-directional, same-region notification streams and don’t require an external dependency. But SSE is HTTP/1.1-bound, doesn’t give you built-in group fan-out, and means our own API process is responsible for holding the connection — which is exactly the scaling problem we wanted Web PubSub to take off our plate for the higher-traffic, multi-tenant broadcast case. Having a lightweight internal SSE manager as a fallback path for specific channels, alongside Web PubSub for the multi-tenant group broadcasts, let us pick the right tool per use case instead of forcing one transport to do everything.

The trade-off worth naming honestly: managed WebSocket infra adds a network hop and an external dependency to your critical path. If Web PubSub has a bad moment, your real-time layer degrades, not your whole app — but you do need a coherent story for reconnects and missed messages, which is why our client always reconciles state (re-fetching the enquiry list) on reconnect rather than trusting the socket alone as source of truth. Real time should feel instant, but it should never be the *only* copy of the truth.

Where this is heading: Microsoft Foundry and AI-assisted procurement

Real-time delivery solves the “did anyone notice” problem. It doesn’t solve “should anyone care right now” — and that’s the next layer we’re building toward using **Microsoft Foundry**. The roadmap isn’t about bolting on a chatbot; it’s about using the same event stream that already flows through Web PubSub as the trigger surface for AI-assisted procurement work: surfacing which incoming quotations are actually competitive against a buyer’s history the moment they land, flagging RFQ responses that look incomplete or off-spec before a buyer wastes time reviewing them, and summarizing multi-message supplier negotiation threads so a busy buyer can catch up in seconds instead of scrolling.

That’s a deliberate sequencing choice: get the plumbing for instant, reliable, multi-tenant event delivery right first, because AI-assisted insights are only as useful as the moment they’re delivered. An intelligent suggestion that shows up after a buyer already made the call isn’t intelligent, it’s noise. Foundry’s role here is to sit downstream of the events we already broadcast — not to replace the real-time layer, but to make what gets pushed through it smarter.

The actual point

None of this is real time for its own sake. Procurement is a human coordination problem wearing software’s clothing, and the thing that keeps a buyer or supplier engaged with a platform instead of falling back to email is the same thing that keeps anyone engaged with any tool: it responds when something happens. Getting that right on Azure — Web PubSub for scoped, multi-tenant push; SSE where it’s simpler; a clear boundary between “the socket says” and “the database says” — was the unglamorous infrastructure work that made the rest of the product possible. The AI layer we’re building next only matters because that foundation is already solid.

Leave a Comment

Your email address will not be published. Required fields are marked *

🚀 Sandbox access is now live — try Enquirica on your own terms. No sales call required.

X