Azure Functions is a common landing point for Acumatica outbound events because it needs no server to run and scales to near-zero cost between triggers. The integration is not a Business Events feature — Acumatica has no native "Azure" subscriber — it's a custom subscriber that happens to call an HTTP-triggered function.
HTTP trigger vs. queue trigger
Two shapes are common. An HTTP-triggered function receives the event directly from Acumatica's outbound queue processor as a POST request — simplest to set up, but Acumatica is now waiting on Azure's response time, so timeouts and cold starts matter. A queue-triggered function instead reads from an Azure Storage Queue or Service Bus queue that Acumatica writes to directly using the Azure SDK — this decouples Acumatica from the function's execution time entirely, at the cost of one more moving part.
For anything beyond a quick internal notification, the queue-triggered shape is worth the extra setup: a cold Azure Function can take a few seconds to spin up, and that's a few seconds Acumatica's outbound processor is blocked on if it's calling the function synchronously over HTTP.
Authenticating the call
Azure Functions with HTTP triggers support function-level keys out of the box, which is the minimum viable option — store the key in Acumatica as an encrypted custom setting, not hardcoded in the subscriber. For anything handling financial data, put the function behind Azure AD app registration and have the subscriber acquire a token via client credentials flow before calling it; function keys alone are a shared secret with no expiry and no audit trail.
var token = await tokenProvider.GetTokenAsync(scope: functionAppScope);
var request = new HttpRequestMessage(HttpMethod.Post, functionUrl)
{
Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Headers.Add("X-Idempotency-Key", eventId);
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
throw new PXException($"Function call failed: {response.StatusCode}");
What the function should do on its side
The function's job is almost never "do the work" — it's "translate and hand off." A function receiving a shipment-confirmed event typically validates the payload, maps Acumatica field names to whatever the downstream system expects, and either calls that system directly (if it's fast and reliable) or drops the translated message onto another queue for a slower downstream consumer. Keeping transformation and delivery separate makes each side independently testable.
When something goes wrong three integrations downstream, the only way back to "which Acumatica record caused this" is a reference number that survived every hop. Carry it as a field in every payload and log it at every stage, even the ones that feel too simple to need logging.
Retry behavior belongs in the queue, not the function
Azure Functions bound to Storage Queues or Service Bus get retry semantics for free — a function that throws an unhandled exception leaves the message on the queue (or moves it to a dead-letter sub-queue after the configured max delivery count) rather than losing it. An HTTP-triggered function gets none of this automatically; if you go the HTTP route, the retry logic has to live in the Acumatica-side outbound processor described in the webhooks article, and in the dead-letter handling covered in the dead-letter queue article.
Wrapping up
Azure Functions work well as the receiving end of Acumatica business events, but the reliability characteristics come from the trigger binding you choose (queue over HTTP) and the auth model (Azure AD over a bare function key), not from anything Business Events provides automatically.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.