Workflow · Architecture

Acumatica Event-Driven Architecture

Business Events react to Acumatica data changes with no code, but they're built on Generic Inquiries and fire after commit. Here is the decision rule for when to use them and when to drop to a DAC or graph event handler instead.

John Kihiu12 min read

Acumatica's Business Events let you react to a data change — a new case, a released sales order, a bill approval — by sending a notification email or updating a field, all without writing a line of code. They're the first thing I reach for when a client asks "can the system just tell someone when X happens", and they're also the thing I see most often stretched past what they were built for. Knowing where that line sits is the actual skill.

What a Business Event actually is

A Business Event is not its own independent engine — it's a thin automation layer sitting on top of a Generic Inquiry. To build one, you need a GI that already exposes: the fields you want to monitor, the condition (trigger) that should fire the event, and whatever action-schedule or email-notification fields the event will use when it fires. Acumatica polls that GI-backed condition and, when a record matches, runs the configured action — usually an email, sometimes a field update. If you've never had to write a GI with a filter condition before setting up a Business Event, that's unusual; the GI is doing the actual detection work, the Business Event is just wiring the trigger to an action.

The part nobody mentions in the demo

Because a Business Event is GI-driven, it inherits the GI's relationship to the database: it's evaluated against committed data, on a schedule, not synchronously as part of the transaction that changed the record. In practice this means a Business Event is a polling reaction, not an interception. The record changes, the change commits, and at some later point (seconds to minutes depending on how the event is scheduled) the condition gets evaluated and the action fires. That's fine for "notify the AP clerk when a bill over $10,000 gets approved" — nobody needs that email synchronously. It is not fine for anything where you need to block, modify, or validate the operation before it commits, or where a few minutes of lag changes the outcome.

Business Events cannot stop a transaction

A Business Event fires after the fact. It cannot reject a save, force a field to a different value before the row is written, or prevent a duplicate from being created in the first place. If the requirement is "don't let this happen," you're not looking for a Business Event — you're looking for a RowUpdating or RowInserting handler that can cancel the operation with a PXSetPropertyException before it commits.

A real example: Business Event feeding an Import Scenario

One of the more useful patterns I've built repeatedly: a Business Event watches for a condition — say, a new opportunity assigned to a sales team — and instead of just emailing someone, its action triggers an Import Scenario that creates and assigns a follow-up task automatically. The GI supplies the trigger and the source data, the Import Scenario supplies the "go create this related record" logic, and the whole thing runs with zero custom code. This is exactly the kind of automation Business Events are for: turning a data condition into a downstream record, on a delay measured in minutes, with no risk if it runs a little late.

When you drop to a graph or DAC event handler

The moment the requirement needs any of the following, stop trying to force it into a Business Event and write the handler instead:

C# · DAC EVENT HANDLER
public class SOOrderEntry_RowEventsExt : PXGraphExtension
{
    protected virtual void _(Events.RowUpdating e)
    {
        var row = (SOOrder)e.Row;
        var newRow = (SOOrder)e.NewRow;
        if (row == null || newRow == null) return;

        // Block the change synchronously, before commit -
        // a Business Event can only react after this row is saved.
        if (row.Status == "Completed" && newRow.OrderTotal != row.OrderTotal)
        {
            throw new PXSetPropertyException(
                "Cannot change the total of a completed order.");
        }
    }

    protected virtual void _(Events.RowUpdated e)
    {
        var row = (SOOrder)e.Row;
        if (row == null) return;

        // Reacts in-transaction, not on the next Business Event
        // polling cycle - use this when timing actually matters.
        if (row.Status == "Open" && row.CreditHold == true)
        {
            PXTrace.WriteInformation($"Order {row.OrderNbr} flagged on hold.");
        }
    }
}

The decision rule

Ask two questions. Does the reaction need to happen before the transaction commits, or is "eventually, within a few minutes" acceptable? And is the trigger condition expressible as a GI filter, or does it need branching logic across related entities? If the answer is "after commit is fine" and "yes, a GI filter can express it," build a Business Event — it's less code, no deployment, and anyone on the functional side can maintain it later. If either answer is no, write the DAC or graph event handler. Don't build a Business Event on a GI so contorted that it takes longer to design than the equivalent ten lines of C#, and don't write a custom handler for a plain "email someone when this field changes" requirement that a GI and a Business Event already solve for free.

Wrapping up

Business Events are a real no-code automation layer, but they are GI-driven and post-commit by construction — they poll a Generic Inquiry's trigger condition and react after the row is already saved. That makes them excellent for notifications and downstream record creation like the Business-Event-to-Import-Scenario pattern, and wrong for anything that needs to validate, block, or react inside the transaction itself. When the requirement needs synchronous interception, go straight to RowUpdating, RowInserted, or FieldDefaulting on the DAC or graph — don't spend a day trying to make a GI trigger do a code event handler's job.

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.