D365FO integration using Business Events




In the modern ERP landscape, "real-time" is no longer a luxury—it’s a requirement. Dynamics 365 Finance & Operations (D365FO) meets this demand through Business Events.

Instead of external systems constantly "polling" D365FO for changes (which is resource-heavy and slow), Business Events flip the script. The system actively pushes a notification the moment a business process—like a Purchase Order confirmation or a Workflow approval—occurs.


1. The Architecture: How It Works

The Business Events framework is built for high-performance, asynchronous communication. It is designed to send lightweight payloads to external "endpoints."

  • Trigger: A business process completes (e.g., an invoice is posted).

  • Business Event: The framework captures the event and its associated data (Contract).

  • Outbound Queue: The event is placed in a staging table.

  • Batch Processing: Dedicated batch threads pick up these events and send them to the configured endpoint.

  • Endpoint: A listener (like Power Automate or Azure Service Bus) receives the JSON payload.

Key Configuration Parameters

In System administration > Setup > Business events > Business events parameters, you can tune the engine:

  • Retry count: Defaults to 3.

  • Bundle size: Determines how many events are grouped for a single thread.

  • Processing threads: Up to 4 dedicated threads per AOS.


2. Supported Endpoints

D365FO doesn't "know" what the external system is; it only knows where to send the data. Common endpoints include:

Endpoint TypeBest For...
Azure Service Bus (Queue/Topic)High-volume, reliable enterprise messaging.
Azure Event GridHighly scalable, event-driven reactive programming.
Power Automate (HTTPS)Low-code automation and simple notifications (Email/Teams).
Azure Event HubsBig data streaming and telemetry.

3. Creating a new Business Event

To create a new business event, for example, when a vendor is created. You to do the following in a high level steps:
1. Create the Business Event Base Class (initialises the business event contract)
2. Create the Business Event Contract Class (For the payload).
3. trigger (define the trigger points)

1. Business Base Class:

//Decorate the business event class
BusinessEvents(classStr(VendorCreatedContract), "VendorCreated", "Vendor Created Event", ModuleAxapta::Vendor)]
public final class VendorCreatedBusinessEvent extends BusinessEventsBase
{
    private VendTable vendTable;

//Create a parameter for the vend table
    private VendTable parmVendtable(VendTable _VendTable =
    VendTable)
    {
        VendTable = _VendTable;
        return VendTable;
    }

// Create an instance of the Business Event 
    public static VendorCreatedBusinessEvent newFromVendTable(VendTable _vendTable)
    {
        VendorCreatedBusinessEvent event = new VendorCreatedBusinessEvent();
        event.parmVendTable(_vendTable);
        return event;
    }

//Initialise the Business event contract
    [Wrappable(true), Replaceable(true)]
    public BusinessEventsContract buildContract()
    {
        return VendorCreatedContract::newFromVendTable(vendTable);
    }
}

2. Business Event Contract Class:

/// <summary>
/// The data contract for a VendorCreatedBusinessEvent
/// </summary>
[DataContract]
public final class VendorCreatedContract extends BusinessEventsContract
{
    private VendAccount vendAccount;
    private VendGroup groupId;
    private Currencycode currencycode;
    
    /// <summary>
    /// Creates a VendorCreatedContract from a VendTable record.
    /// </summary>
    /// <param name = "_VendTable"> VendTable record</param>
    /// <returns>A VendorCreatedContract</returns>
    public static VendorCreatedContract newFromVendTable(VendTable _vendTable)
    {
        var contract = new VendorCreatedContract();
        contract.initialize(_vendTable);
        return contract;
    }
//Payloads definition
    protected void initialize(VendTable _vendTable)
    {
        vendAccount   = _vendTable.VendAccount;
        vendGroup      = _vendTable.vendGroup;
        currencycode  = _vendTable.currency;
       
    }
// New instance
    private void new()
    {
    }

//Vendor account
    [DataMember('VendAccount'), BusinessEventsDataMember("@AccountsReceivable:VendAccount")]
    public VendAccount parmVendAccount(VendAccount _VendAccount
    = VendAccount)
    {
        VendAccount = _VendAccount;
        return VendAccount;
    }

//Vendor group
    [DataMember('VendGroup'), BusinessEventsDataMember("@AccountsReceivable:VendGroup")]
    public VendGroup parmVendGroup(VendGroup _VendGroup = VendGroup)
    {
    VendGroup = _VendGroup;
    return VendGroup;
    }

//Currency code
    [DataMember('CurrencyCode'), BusinessEventsDataMember("@AccountsReceivable:CurrencyCode")]
    public CurrencyCode parmCurrencyCode(CurrencyCode _CurrencyCode = CurrencyCode)
    {
        CurrencyCode = _CurrencyCode;
        return CurrencyCode;
    }
    
}


3. The trigger:

public static class DevVendorCreatedBusinessEventTrigger_Extension
{
    
    /// <summary>
    ///Send the business event on vendor record creation.
    /// </summary>
    /// <param name="sender">Vendor Table</param>
    /// <param name="e"></param>
    [DataEventHandler(tableStr(VendTable), DataEventType::Inserted)]
    public static void VendTable_onInserted(Common sender, DataEventArgs e)
    {
      
        VendTable vendTable = sender;
        VendorCreatedBusinessEvent businessEvent = VendorCreatedBusinessEvent::newFromVendTable(vendTable);
        if(businessEvent)
        {
            businessEvent.send();
        }
    }
}


4. You can then call the business event trigger on:a. Azure Service Bus.b. Power automate.C. Azure logic apps

4. Pro-Tips for Implementation

  • Rebuild the Catalog: After deploying new X++ code, go to the Business Events Catalog and click Manage > Rebuild business events catalog to see your new event.

  • Keep Payloads Slim: Business events are not for bulk data transfer. If you need to send 1,000 fields, send the Primary Key via a Business Event and have the external system call an OData entity to fetch the rest or Data Management Framework.

  • Idempotency: The framework includes a ControlNumber in the payload. Use this in your consuming system to ensure you don't process the same event twice.


Comments

Popular posts from this blog

Ledger Dimension Facade Class

Integration Capabilities and Support in Microsoft Dynamics 365 Finance & Operations (F&O) - An Overview

Performance and Monitoring in dynamics 365 F&O.