Disallowing changing Azure SQL database connection policy using Azure policy
September 11, 2025New in Microsoft AppSource: August 20-31, 2025
September 12, 2025š PrefaceĀ
This postĀ demonstratesĀ one method to exportĀ Dynamics 365 Customer Engagement (CE) DataverseĀ organization data using theĀ Office 365 Management Activity APIĀ andĀ Azure Functions. It isĀ feasibleĀ for customers to build a custom lake-house architecture with this feed, enabling advanced analytics, archiving, or ML/AI scenarios.Ā
Ā
š§ When to Use This Custom IntegrationĀ
While Microsoft offers powerful native integrations likeĀ Dataverse Synapse LinkĀ andĀ Microsoft Fabric, this custom solution isĀ observedĀ implemented and relevant in the following scenarios:Ā
- Third-party observability and security tools already use this approachĀ
Solutions such as SplunkĀ and other enterprise-grade platforms commonly implement integrations based on theĀ Office 365 Management Activity APIĀ to ingest tenant-wide audit data. This makes it easier for customers to align with existing observability pipelines or security frameworks.Ā
- Customers opt out of Synapse Link or FabricĀ
Whether due to architectural preferences, licensing constraints, or specific compliance requirements, some customers choose not to adopt Microsoftās native integrations. The Office Management API offersĀ a viableĀ alternative for buildingĀ custom data export and monitoring solutionsĀ tailored to their needs.Ā
Ā
šÆ Why Use the Office 365 Management Activity API?Ā
- Tenant-wide Data Capture: Captures audit logs and activity data across all Dataverse orgs in a tenant.Ā
- Integration Flexibility: Enables export to Cosmos DB, cold storage, or other platforms for analytics, compliance, or ML/AI.Ā
- Third-party Compatibility: Many enterprise tools use similar mechanisms to ingest and archive activity data.Ā
Ā
šļø Architecture OverviewĀ
- Azure Function App (.NET Isolated): Built as webhook, processes notifications, fetches audit content, and stores filtered events in Cosmos DB.Ā
- Cosmos DB: Stores audit events for further analysis or archiving.Ā
- Application Insights: Captures logs and diagnostics for troubleshooting.Ā
Ā
š ļø Step-by-Step ImplementationĀ
1. Prerequisites
- Azure subscriptionĀ
- Dynamics 365 CE environment (Dataverse)Ā
- Azure Cosmos DB account (SQL API)Ā
- Office 365 tenant admin rightsĀ
- Enable Auditing in Dataverse orgĀ
2. Register an Azure AD AppĀ
- Go toĀ Azure Portal > Azure Active Directory > App registrations >Ā New registrationĀ
- Note:Ā
- Application (client) IDĀ
- Directory (tenant) ID
- Create a client secretĀ
- Grant API permissions:Ā
- ActivityFeed.ReadĀ
- ActivityFeed.ReadDlpĀ
- ServiceHealth.ReadĀ
- GrantĀ admin consentĀ
3. Set Up Cosmos DB
- Create a Cosmos DB account (SQL API)Ā
- Create:Ā
- Database: officewebhookĀ
- Container: dynamicsevents
- Partition key: /tenantIdĀ
- Note endpoint URI and primary key
4. Create the Azure Function App
- Use Visual Studio or VS Code
- Create a new Azure Functions project (.NET 8 Isolated Worker)Ā
- Add NuGet packages:Ā
- Microsoft.Azure.Functions.WorkerĀ
- Microsoft.Azure.CosmosĀ
- Newtonsoft.JsonĀ
- Ā
Function Logic:Ā
- Webhook validationĀ
- Notification processing
- Audit content fetching
- Event filteringĀ
- Storage in Cosmos DB
5. Configure Environment Variables
{
“OfficeApiTenantId”: “”,
“OfficeApiClientId”: “”,
“OfficeApiClientSecret”: “”,
“CrmOrganizationUniqueName”: “”,
“CosmosDbEndpoint”: “”,
“CosmosDbKey”: “”,
“CosmosDbDatabaseId”: “officewebhook”,
“CosmosDbContainerId”: “dynamicsevents”,
“EntityOperationsFilter”: {
“incident”: [“create”, “update”],
“account”: [“create”]
}
}
Ā
6. Deploy the Function App
- Build and publish using Azure Functions Core Tools or Visual StudioĀ
- Restart the Function App from Azure PortalĀ
- Monitor logs via Application InsightsĀ
Ā
š How to Subscribe to the Office 365 Management Activity API for Audit NotificationsĀ
To receive audit notifications, you must first subscribe to the Office 365 Management Activity API. This is a two-step process:Ā
1. Fetch an OAuth2 Token
Authenticate using your Azure AD app credentials to get a bearer token:Ā
# Define your Azure AD app credentials
$tenantId = “”
$clientId = “”
$clientSecret = “”
# Prepare the request body for token fetch
$body = @{
grant_type = “client_credentials”
client_id = $clientId
client_secret = $clientSecret
scope = “https://manage.office.com/.default”
}
# Fetch the OAuth2 token
$tokenResponse = Invoke-RestMethod -Method Post -Uri “https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token” -Body $body
$token = $tokenResponse.access_token
Ā Ā Ā
Ā 2. Subscribe to the Content Type
Use the token to subscribe to the desired content type (e.g., Audit.General):Ā
$contentType = “Audit.General”
$headers = @{
Authorization = “Bearer $token”
“Content-Type” = “application/json”
}
$uri = “https://manage.office.com/api/v1.0/$tenantId/activity/feed/subscriptions/start?contentType=$contentType”
$response = Invoke-RestMethod -Method Post -Uri $uri -Headers $headers
$response
Ā
āļø How the Azure Function WorksĀ
šø TriggerĀ
The Azure Function is triggered by notifications from the Office 365 Management Activity API. These notifications include audit events across your entire Azure tenantānot just Dynamics 365.Ā
šø Filtering LogicĀ
Each notification is evaluated against your business rules:Ā
- Organization matchĀ
- Entity type (e.g., incident, account)Ā
- Operation type (e.g., create, update)Ā
These filters are defined in the EntityOperationsFilter environment variable:Ā
{
“incident”: [“create”, “update”],
“account”: [“create”]
}
Ā Ā Ā
šø ProcessingĀ
- If the event matches your filters, the function fetches the full audit data and stores it in Cosmos DB.Ā
- If not, the event is ignored.Ā
Ā
š Code Explanation: The Run Method
1. Webhook Validation
string validationToken = query[“validationToken”];
if (!string.IsNullOrEmpty(validationToken)) {
await response.WriteStringAsync(validationToken);
response.StatusCode = HttpStatusCode.OK;
return response;
}
2. Notification Handling
var notifications = JsonConvert.DeserializeObject(requestBody);
foreach (var notification in notifications)
{
if (notification.contentType == “Audit.General” && notification.contentUri != null)
{
// Process each notification
}
}
Ā Ā
3. Bearer Token Fetch
string bearerToken = await GetBearerTokenAsync(log);
if (string.IsNullOrEmpty(bearerToken)) continue;
Ā
4. Fetch Audit Content
var requestMsg = new HttpRequestMessage(HttpMethod.Get, contentUri);
requestMsg.Headers.Authorization = new AuthenticationHeaderValue(“Bearer”, bearerToken);
var result = await httpClient.SendAsync(requestMsg);
if (!result.IsSuccessStatusCode) continue;
var auditContentJson = await result.Content.ReadAsStringAsync();
Ā
5. Deserialize and Filter Audit Records
var auditRecords = JsonConvert.DeserializeObject(auditContentJson);
foreach (var eventData in auditRecords) {
string orgName = eventData.CrmOrganizationUniqueName ?? “”;
string workload = eventData.Workload ?? “”;
string entityName = eventData.EntityName ?? “”;
string operation = eventData.Message ?? “”;
if (workload != “Dynamics 365” && workload != “CRM” && workload != “Power Platform”) continue;
if (!entityOpsFilter.ContainsKey(entityName)) continue;
if (!entityOpsFilter[entityName].Contains(operation)) continue;
// Store in Cosmos DB
}
Ā Ā
Ā 6. Store in Cosmos DB
var cosmosDoc = new {
id = Guid.NewGuid().ToString(),
tenantId = notification.tenantId,
raw = eventData
};
var partitionKey = (string)notification.tenantId;
var resp = await cosmosContainer.CreateItemAsync(cosmosDoc, new PartitionKey(partitionKey));
Ā
Ā 7. Logging and Error Handling
log.LogInformation($”Stored notification in Cosmos DB for contentUri: {notification.contentUri}, DocumentId: {cosmosDoc.id}”);
catch (Exception dbEx) {
log.LogError($”Error storing notification in Cosmos DB: {dbEx.Message}”);
}
Ā Ā
Ā š§ ConclusionĀ
This solution provides a robust, extensible pattern for exporting Dynamics 365 CE Dataverse org data to Cosmos DB using the Office 365 Management Activity API. Solution architects can use this as a reference for building or evaluating similar integrations, especially when working with third-party archiving or analytics solutions.Ā