Live events: Microsoft 365
This is the customer-admin setup for near-real-time SharePoint and OneDrive sync on an on-prem appliance. Microsoft Graph publishes a small change notification to the customer’s Event Hub; the appliance consumes it outbound-only and runs the normal delta feed. No public webhook or inbound firewall rule is required.
One setup serves both connectors: OneDrive drives and SharePoint document
libraries use the same /drives/{id}/root subscription resource, and the
appliance runs one shared Event Hubs consumer for the whole Microsoft Graph
family. The scheduled incremental sync remains the reconciliation fallback if
an event is delayed or missed.
What the customer must provide
Section titled “What the customer must provide”- An Azure subscription in the same Entra tenant as SharePoint.
- Permission to create Event Hubs resources and Azure role assignments
(
OwnerorUser Access Administratorplus resource creation is enough). - The appliance’s Entra application. A dedicated Event Hubs receiver application also needs a client secret kept in the customer’s secret manager.
- A SharePoint connection whose first sync has discovered its document libraries.
Use browser-based Azure CLI login:
az login --tenant CUSTOMER_TENANT_IDDo not use --use-device-code when Entra Security Defaults blocks the
device-code flow (Microsoft enabled that block for new tenants beginning
July 1, 2026), and do not disable Security Defaults to make this setup work.
Create the Event Hub
Section titled “Create the Event Hub”Choose customer-specific names:
SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000"RESOURCE_GROUP="legalmemory"LOCATION="westeurope"NAMESPACE="lm-graph-events-customer"EVENT_HUB="legalmemory"CONSUMER_GROUP='$Default'
az account set --subscription "$SUBSCRIPTION_ID"az provider register --namespace Microsoft.EventHub --waitaz group create --name "$RESOURCE_GROUP" --location "$LOCATION"az eventhubs namespace create \ --resource-group "$RESOURCE_GROUP" \ --name "$NAMESPACE" \ --location "$LOCATION" \ --sku Basic \ --capacity 1 \ --minimum-tls-version 1.2az eventhubs eventhub create \ --resource-group "$RESOURCE_GROUP" \ --namespace-name "$NAMESPACE" \ --name "$EVENT_HUB" \ --partition-count 2 \ --retention-time-in-hours 24 \ --cleanup-policy DeleteBasic namespaces use the built-in $Default consumer group. If the hub is
shared with another consumer, use Standard tier and create a dedicated group:
az eventhubs eventhub consumer-group create \ --resource-group "$RESOURCE_GROUP" \ --namespace-name "$NAMESPACE" \ --eventhub-name "$EVENT_HUB" \ --name legalmemoryGrant the two Event Hubs roles
Section titled “Grant the two Event Hubs roles”There are two different identities, and neither role should be granted at subscription scope:
- Microsoft Graph Change Tracking publishes notifications and needs Azure Event Hubs Data Sender on this event hub.
- The appliance’s Entra application consumes notifications and needs Azure Event Hubs Data Receiver on this event hub.
In the Azure portal: Event Hubs namespace → Event Hubs → <hub> → Access control (IAM), add the two role assignments. The equivalent CLI:
EVENT_HUB_ID="$(az eventhubs eventhub show \ --resource-group "$RESOURCE_GROUP" \ --namespace-name "$NAMESPACE" \ --name "$EVENT_HUB" \ --query id -o tsv)"
GRAPH_CHANGE_TRACKING_OBJECT_ID="$(az ad sp list \ --filter \"appId eq '0bf30f3b-4a52-48df-9a82-234910c4a086'\" \ --query '[0].id' -o tsv)"LM_APP_OBJECT_ID="$(az ad sp show \ --id CUSTOMER_APPLIANCE_CLIENT_ID \ --query id -o tsv)"
az role assignment create \ --assignee-object-id "$GRAPH_CHANGE_TRACKING_OBJECT_ID" \ --assignee-principal-type ServicePrincipal \ --role "Azure Event Hubs Data Sender" \ --scope "$EVENT_HUB_ID"az role assignment create \ --assignee-object-id "$LM_APP_OBJECT_ID" \ --assignee-principal-type ServicePrincipal \ --role "Azure Event Hubs Data Receiver" \ --scope "$EVENT_HUB_ID"If Microsoft Graph Change Tracking is missing, a tenant administrator must
create its service principal using the globally fixed application id
0bf30f3b-4a52-48df-9a82-234910c4a086. Microsoft documents both the lookup
and the creation request in
Receive change notifications through Azure Event Hubs.
Configure the appliance
Section titled “Configure the appliance”Store the receiver secret in the customer’s secret manager and provide these
deployment values to the app container only, never to the worker or
watcher containers:
KI_MICROSOFT_EVENTS_NOTIFICATION_URL=EventHub:https://lm-graph-events-customer.servicebus.windows.net/eventhubname/legalmemory?tenantId=customer.onmicrosoft.comKI_MICROSOFT_EVENTS_NAMESPACE=lm-graph-events-customer.servicebus.windows.netKI_MICROSOFT_EVENTS_EVENT_HUB=legalmemoryKI_MICROSOFT_EVENTS_CONSUMER_GROUP='$Default'KI_MICROSOFT_EVENTS_TENANT_ID=00000000-0000-0000-0000-000000000000KI_MICROSOFT_EVENTS_CLIENT_ID=00000000-0000-0000-0000-000000000000# Required for a dedicated receiver application. If CLIENT_ID is the same# application used by an active SharePoint connection, the appliance reuses# that connection's encrypted client secret and this may stay empty.KI_MICROSOFT_EVENTS_CLIENT_SECRET=The notification URL (the EventHub: form Microsoft Graph writes to) is
deliberately a separate setting from the AMQP coordinates the appliance
consumes from; keeping them apart prevents a portal value being pasted into
the wrong field and producing a subscription that can never be received.
Restart the app container. After the first SharePoint sync, the appliance
creates one Graph subscription per discovered document library and renews it
three days before its 29-day expiry. Event Hubs partition offsets are stored
in Postgres, so restarts resume from the retained broker position.
Verify
Section titled “Verify”docker compose logs app | rg "Microsoft Event Hubs consumer started"SELECT status, target, external_id, expires_at, last_event_at, last_errorFROM connector_event_subscriptionsWHERE adapter = 'microsoft_graph_sharepoint';Then upload one harmless file and confirm: last_event_at changes, an
event-triggered source-sync run starts, the file is inserted exactly once,
and deleting it produces a tombstone without starting an unrelated insertion
batch.