Inhalte
1. Warum der Wechsel von RERs unumgänglich ist
2. Der moderne Weg: SharePoint Webhooks & Azure Functions
3. Warum Azure Functions statt Logic Apps für die RER-Migration?
4. Architektur & Implementierung einer Webhook-Lösung
5. Implementierung der Azure Function
6. Funktionsweise im Detail
7. Fazit: Jetzt handeln!
Die Microsoft 365-Plattform entwickelt sich kontinuierlich weiter, und mit ihr die Best Practices für SharePoint Online-Erweiterungen. Eine kritische Änderung steht bevor: Microsoft stellt das SharePoint Add-In-Modell ein, was direkte Auswirkungen auf Remote Event Receiver (RERs) hat. Die Unterstützung für Add-Ins, die Azure Access Control Service (ACS)-Authentifizierung verwenden endet am 2. April 2026. Add-Ins ohne ACS werden nach dem 1. Juli 2027 nicht weiter funktionsfähig sein.
Wenn Ihre Organisation noch RERs einsetzt, ist jetzt der richtige Zeitpunkt für eine Migrationsstrategie. Die moderne Alternative: SharePoint Webhooks in Kombination mit Azure Functions.
Unsere erfahrenen Expertinnen und Experten unterstützen Sie gerne bei der Migration.
1. Warum der Wechsel von RERs unumgänglich ist
Remote Event Receiver waren lange der Standard, um auf SharePoint-Ereignisse zu reagieren und externe Logik auszuführen. Dieses Modell hat jedoch entscheidende Nachteile:
- End-of-Life: Microsoft stellt das Add-In-Modell und ACS ein – der wichtigste Grund für eine Migration.
- Technologische Altlast: Das Modell entspricht nicht mehr modernen Cloud-Architekturen.
- Performance-Probleme: Besonders synchrone Events (wie
ItemAdding
) blockieren die Benutzeroberfläche bis zur Antwort des RERs, was zu Verzögerungen führt.
- Komplexe Infrastruktur: Bereitstellung, Authentifizierung und Wartung von Provider-Hosted Add-Ins sind aufwändig und fehleranfällig.
2. Der moderne Weg: SharePoint Webhooks & Azure Functions
SharePoint Webhooks bieten einen effizienten, ereignisgesteuerten Ansatz. Bei Änderungen in Listen oder Bibliotheken sendet SharePoint eine asynchrone Benachrichtigung an Ihren registrierten Endpunkt.
Die Verarbeitung dieser Benachrichtigungen übernehmen idealerweise Azure Functions. Diese Kombination bietet zahlreiche Vorteile:
- Vollständig asynchron: Die Verarbeitung erfolgt entkoppelt von SharePoint, was Performance und Benutzererfahrung verbessert.
- Serverless & automatisch skalierend: Azure Functions passen sich dynamisch dem Bedarf an – ohne Server-Management.
- Sprachflexibilität: Entwickeln Sie in C#, PowerShell, Python, Node.js oder anderen unterstützten Sprachen.
- Pay-as-you-go: Zahlen Sie nur für tatsächliche Ausführungszeit statt für dedizierte Server.
- Nahtlose Azure-Integration: Einfache Anbindung an Queue Storage, Application Insights, Key Vault und andere Azure-Dienste.
3. Warum Azure Functions statt Logic Apps für die RER-Migration?
Obwohl Logic Apps ebenfalls eine leistungsstarke serverlose Option darstellen, bieten Azure Functions für die Migration von RERs entscheidende Vorteile:
Code-Wiederverwendung: Der größte Vorteil – bestehende C#-Geschäftslogik aus RERs kann
oft mit minimalen Anpassungen in Azure Functions übernommen werden.
Entwicklerfreundlichkeit: Azure Functions bieten einen “Code-First”-Ansatz mit voller
Kontrolle über die Implementierung. Sie können komplexe Logik abbilden und spezialisierte Bibliotheken wie CSOM oder das PnP Core SDK direkt einbinden.
Kosteneffizienz: Für code-intensive Aufgaben sind Azure Functions im Consumption Plan
häufig günstiger als Logic Apps, die pro Aktion/Konnektor abrechnen.
Fazit: Logic Apps eignen sich hervorragend für visuelle Workflow-Orchestrierung mit Standard-Konnektoren. Für die Migration bestehender RER-Logik bieten Azure Functions durch Code-Wiederverwendung und Flexibilität meist den effizienteren Weg.
4. Architektur & Implementierung einer Webhook-Lösung
Eine erfolgreiche Migration zu Webhooks erfordert die Beachtung einiger Schlüsselprinzipien:
- Schnelle Antwortzeiten: SharePoint erwartet eine Bestätigung innerhalb von 5 Sekunden vom Webhook-Endpunkt.
- Regelmäßige Erneuerung: Webhook-Registrierungen laufen nach maximal 6 Monaten ab und müssen verlängert werden.
- Minimale Benachrichtigungen: Die Webhook-Benachrichtigung enthält nur grundlegende Informationen – die eigentlichen Änderungen müssen aktiv abgerufen werden.
Benötigte Azure-Ressourcen

- Azure Function App: Hostet den Code für die Trigger. Empfehlenswert ist der Consumption Plan (für automatische Skalierung und niedrige Kosten bei geringer Last) oder ein App Service Plan (für vorhersehbare Kosten und VNet-Integration).
- Azure Storage Account: Dient der asynchronen Verarbeitung via Queue Storage und speichert das letzte ChangeToken pro SharePoint-Liste/Webhook-Subscription.
- Application Insights: Ermöglicht umfassendes Monitoring, Logging und Fehlerdiagnose – unverzichtbar bei verteilten Systemen.
- Azure Key Vault: Sichert Geheimnisse und Zertifikate für die Authentifizierung.
Authentifizierung einrichten
Für die Authentifizierung der Azure Function gegenüber SharePoint wird ein Zertifikat benötigt:
- Generieren Sie ein Zertifikat mit dem PnP PowerShell-Cmdlet
New-PnPAzureCertificate
- Laden Sie das Zertifikat in Ihre App-Registrierung hoch
- Konfigurieren Sie API-Berechtigungen (empfohlen:
Sites.Selected
und User.Read.All
)
- Erteilen Sie der App spezifische SharePoint-Berechtigungen (mindestens “Manage”-Level)
Dies kann per PnP PowerShell erfolgen:
Grant-PnPAzureADAppSitePermission -AppId <Guid> -DisplayName <String> -Permissions Manage
Oder über die Microsoft Graph API:
POST https://graph.microsoft.com/v1.0/sites/<SharePointTenantName>:/<Pfad>:/permission
{
"roles": [ "manage" ],
"grantedToIdentities":
[
{
"application":
{
"id": "<AppId>",
"displayName": "<Name>"
}
}
]
}
5. Implementierung der Azure Function
Die Lösung basiert auf drei Hauptkomponenten:

Time Trigger: Verantwortlich für die Registrierung und Verlängerung der Webhooks
// Beispielhafter C# Code für den Time Trigger
var list = await pnpContext.Web.Lists.GetByTitleAsync(title);
await list.LoadAsync(l => l.Webhooks);
var clientState = _registrationHelper.GetClientState();
var expires = DateTime.UtcNow.AddDays(10);
var webhook = await list.Webhooks.FirstOrDefaultAsync(h => h.NotificationUrl == url && h.ClientState == clientState);
if (webhook != null)
{
webhook.ExpirationDateTime = expires;
await webhook.UpdateAsync();
}
else
{
await list.Webhooks.AddAsync(url, expires, clientState);
}
HTTP Trigger: Empfängt Benachrichtigungen von SharePoint und leitet sie an die Queue weiter
// Beispielhafter C# Code für den HTTP Trigger
[Function("ProcessEvent")]
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
string validationToken)
{
// Prepare the response object
HttpResponseData? response = null;
if (!string.IsNullOrEmpty(validationToken))
{
// If we've got a validationtoken querystring argument
// We simply reply back with 200 (OK) and the echo of the validationtoken
response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
await response.WriteStringAsync(validationToken);
return response;
}
// Deserialize the request body
using var sr = new StreamReader(req.Body);
var jsonRequest = await sr.ReadToEndAsync();
var notifications = JsonSerializer.Deserialize<WebhookNotification>(jsonRequest, _jsonSerializerOptions);
if (notifications != null)
{
var clientState = _registrationHelper.GetClientState();
// Then process every single event in the notification body
foreach (var notification in notifications.Value.DistinctBy(n => n.Resource))
{
// check expected client state to verify that SPO is the caller
if (notification.ClientState != clientState)
{
return req.CreateResponse(HttpStatusCode.Forbidden);
}
var queue = queueServiceClient.GetQueueClient("spo-webhooks");
await queue.CreateIfNotExistsAsync();
var message = JsonSerializer.Serialize(notification);
await queue.SendMessageAsync(
Convert.ToBase64String(
System.Text.Encoding.UTF8.GetBytes(message)));
}
}
// We need to return an OK response within 5 seconds
response = req.CreateResponse(HttpStatusCode.OK);
return response;
}
Queue Trigger: Verarbeitet die Benachrichtigungen asynchron und ruft die tatsächlichen Änderungen ab
[Function("QueueProcessEvent")]
public async Task Run([QueueTrigger("spo-webhooks")] string queueMessage)
{
if (!string.IsNullOrEmpty(queueMessage))
{
// queueMessage is already decoded by the QueueTrigger (Base-64)
var notification = JsonSerializer.Deserialize<WebhookNotificationEvent>(queueMessage, _jsonSerializerOptions);
if (notification != null)
{
//Create PnP Context here
// Define a query for the last 100 changes happened, if the type is either add or update.
var changeQuery = new ChangeQueryOptions(false, true)
{
FetchLimit = 100,
Item = true,
Add = true,
Update = true
};
// Provide the ChangeToken
var lastChangeToken = await GetLatestChangeTokenAsync(notification.Resource);
if (lastChangeToken != null)
{
changeQuery.ChangeTokenStart = new ChangeTokenOptions(lastChangeToken);
}
else
{
// Generate a default Change Token
var ticks = DateTime.UtcNow.AddHours(-2).ToUniversalTime().Ticks.ToString();
changeQuery.ChangeTokenStart = new ChangeTokenOptions(string.Format("1;3;{0};{1};-1", notification.Resource, ticks));
}
// Use GetChanges against the list with ID notification.Resource, which is the target list
var targetList = await pnpContext.Web.Lists.GetByIdAsync(Guid.Parse(notification.Resource));
var changes = await targetList.GetChangesAsync(changeQuery);
// prepare changes for processing, remove duplicates
List<IChangeItem> changeItems = changes
.Where(c => c is IChangeItem && c.IsPropertyAvailable<IChangeItem>(i => i.ItemId))
.Cast<IChangeItem>()
.OrderBy(c => c.Time)
.DistinctBy(c => c.ItemId)
.ToList();
// Save the latest change token
await SaveLatestChangeTokenAsync(changes[changes.Count - 1].ChangeToken);
foreach (var changeItem in changeItems)
{
try
{
var targetItem = await targetList.Items.GetByIdAsync(changeItem.ItemId);
if (targetItem != null)
{
// Process the change
await service.ProcessItemAsync(changeItem, targetItem);
}
}
catch (Exception ex)
{
// log error
}
}
}
}
}
6. Funktionsweise im Detail
Die Webhook-Architektur folgt einem durchdachten Ablauf, der die Zuverlässigkeit und Skalierbarkeit der Lösung sicherstellt:
Webhook-Registrierung und -Erneuerung:
- Der Time Trigger läuft in regelmäßigen Intervallen und prüft alle konfigurierten SharePoint-Listen.
- Für jede Liste wird geprüft, ob bereits ein Webhook registriert ist. Falls ja, wird das Ablaufdatum verlängert.
- Falls keine Webhook existiert, wird eine neue registriert.
- Diese automatische Verlängerung ist entscheidend, da SharePoint-Webhooks nach maximal 6 Monaten ablaufen und dann keine Benachrichtigungen mehr senden.
Benachrichtigungsempfang und -validierung:
- Bei Änderungen in einer überwachten Liste sendet SharePoint eine HTTP-POST-Anfrage an den HTTP-Trigger.
- Der HTTP-Trigger prüft zunächst, ob es sich um eine Validierungsanfrage handelt (Parameter
validationToken
). In diesem Fall wird der Token einfach zurückgesendet, um die Webhook-Registrierung abzuschließen.
- Bei einer echten Benachrichtigung wird der ClientState überprüft, um sicherzustellen, dass die Anfrage tatsächlich von SharePoint stammt und nicht von einem unbefugten Dritten.
- Die Benachrichtigung enthält nur minimale Informationen: die ID der Liste, in der eine Änderung stattgefunden hat, aber keine Details zur Änderung selbst.
- Da SharePoint eine Antwort innerhalb von 5 Sekunden erwartet, wird die eigentliche Verarbeitung asynchron durchgeführt: Die Benachrichtigung wird in eine Azure Storage Queue geschrieben und sofort mit 200 OK bestätigt.
Asynchrone Verarbeitung und Änderungsabfrage:
- Der Queue Trigger wird automatisch aktiviert, sobald eine neue Nachricht in der Queue erscheint.
- Für die Abfrage der tatsächlichen Änderungen wird das SharePoint-ChangeToken-Konzept verwendet:
- Das ChangeToken markiert einen Zeitpunkt in der Änderungshistorie einer Liste.
- Für jede Liste wird das zuletzt verarbeitete ChangeToken in Azure Storage gespeichert.
- Mit dem SharePoint-Endpunkt
GetChanges
werden alle Änderungen seit diesem Token abgerufen.
- Für jedes geänderte Element wird die eigentliche Geschäftslogik ausgeführt, die je nach Anwendungsfall variiert.
- Nach erfolgreicher Verarbeitung wird das neueste ChangeToken gespeichert, um beim nächsten Aufruf den richtigen Startpunkt zu haben.
Fehlerbehandlung und Robustheit:
- Die Queue-basierte Architektur bietet automatische Wiederholungsversuche bei temporären Fehlern.
- Durch die Trennung von Benachrichtigungsempfang (HTTP-Trigger) und Verarbeitung (Queue-Trigger) wird sichergestellt, dass keine Benachrichtigungen verloren gehen, selbst wenn die Verarbeitung zeitweise fehlschlägt.
- Die Verwendung von Application Insights ermöglicht umfassendes Monitoring und Alerting bei Problemen.
Diese Architektur bietet entscheidende Vorteile gegenüber dem alten RER-Modell:
Sie ist vollständig asynchron, skaliert automatisch mit der Last und bietet eine robuste Fehlerbehandlung. Die klare Trennung der Verantwortlichkeiten zwischen den verschiedenen Triggern macht die Lösung wartbar und erweiterbar.
7. Fazit: Jetzt handeln!
Die Abkündigung von SharePoint Add-Ins und RERs ist beschlossene Sache. Die Migration zu Webhooks und Azure Functions ist nicht nur notwendig, sondern bietet auch erhebliche Vorteile:
- Bessere Performance durch asynchrone Verarbeitung
- Höhere Skalierbarkeit durch serverlose Architektur
- Geringere Kosten durch verbrauchsbasierte Abrechnung
- Einfachere Wartung durch moderne Entwicklungsmodelle
Besonders wertvoll: Die Möglichkeit, bestehende RER-Logik in Azure Functions wiederzuverwenden, kann den Migrationsaufwand deutlich reduzieren.
Starten Sie jetzt mit der Analyse Ihrer RERs und planen Sie den Umstieg – die Deadline 2026 rückt näher!