When server-side event receivers couldn't follow us into SharePoint Online, remote event receivers (RERs) filled the gap. The idea is elegant: SharePoint calls your web service when something happens, passing the event details over the wire. In practice RERs have real limitations that pushed Microsoft — and the rest of us — towards webhooks. This article covers both, and when to choose which.
The remote event receiver model
An RER is a SOAP web service endpoint, hosted in your provider-hosted add-in, that implements IRemoteEventService. SharePoint invokes it for the same event types you knew from the server model — but across the network:
- Synchronous "-ing" events (
ItemAdding,ItemUpdating): SharePoint waits for your response and you can cancel the operation. Because it's a network call, you have a strict time budget. - Asynchronous "-ed" events (
ItemAdded,ItemUpdated): fire-and-forget; you cannot cancel. - App lifecycle events (
AppInstalled,AppUninstalling): the most reliable use of RERs — run setup/teardown when your add-in is installed or removed.
Implementing the service
The Visual Studio add-in template generates the WCF service and the RemoteEventReceiver1.svc. Your job is the ProcessEvent (synchronous) and ProcessOneWayEvent (asynchronous) methods:
public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
{
var result = new SPRemoteEventResult();
if (properties.EventType == SPRemoteEventType.ItemAdding)
{
var value = properties.ItemEventProperties
.AfterProperties["ContractValue"];
if (value != null && Convert.ToDecimal(value) < 0)
{
result.ErrorMessage = "Contract value cannot be negative.";
result.Status = SPRemoteEventServiceStatus.CancelWithError;
}
}
return result;
}
public void ProcessOneWayEvent(SPRemoteEventProperties properties)
{
if (properties.EventType == SPRemoteEventType.ItemAdded)
{
using (var ctx = TokenHelper.CreateRemoteEventReceiverClientContext(properties))
{
if (ctx != null)
{
var item = properties.ItemEventProperties
.ListItem; // resolve via ctx as needed
// post-commit work
ctx.ExecuteQuery();
}
}
}
}
TokenHelper.CreateRemoteEventReceiverClientContext builds an authenticated CSOM context from the token SharePoint includes in the event payload — that's how your remote code calls back into the site that raised the event.
Registering the receiver
RERs are declared in the add-in's AppManifest.xml (for app events) or attached to a list at runtime via CSOM, pointing at your service URL:
web.Lists.GetByTitle("Contracts").EventReceivers.Add(new EventReceiverDefinitionCreationInformation
{
EventType = EventReceiverType.ItemAdded,
ReceiverName = "ContractItemAdded",
ReceiverUrl = "https://contoso-addin.azurewebsites.net/RemoteEventReceiver1.svc",
SequenceNumber = 10000
});
ctx.ExecuteQuery();
Where RERs hurt
RERs work, but production taught the community several painful lessons:
- No delivery guarantee. If your endpoint is down or slow when an async event fires, SharePoint does not retry. The event is simply lost.
- Tight timeouts on synchronous events. A slow validation means the user's save fails — or worse, hangs.
- Cold starts. If your service is on a platform that idles out, the first event after a quiet period can time out.
- SOAP plumbing. The WCF endpoint is heavier and more brittle than a simple HTTP handler.
Enter webhooks
SharePoint webhooks address the reliability problem with a fundamentally different contract. Instead of sending you the event details, SharePoint sends a lightweight notification that "something changed in this list," and you call back to find out what — using the list's change log. Crucially, webhooks retry failed deliveries and your subscription has an explicit lifecycle.
Registering a webhook is a single REST call specifying the list, your notification URL, and an expiry (max 180 days):
POST .../_api/web/lists('list-guid')/subscriptions
Content-Type: application/json
{
"resource": "https://contoso.sharepoint.com/_api/web/lists('list-guid')",
"notificationUrl": "https://contoso-fn.azurewebsites.net/api/spwebhook",
"expirationDateTime": "2018-03-11T00:00:00+00:00",
"clientState": "contoso-secret"
}
Your endpoint must first answer the validation handshake: when you register, SharePoint immediately calls your URL with a validationtoken query parameter, and you must echo it back as plain text within 5 seconds. After that, change notifications arrive as small JSON payloads — and you respond fast (return HTTP 200 within seconds), queue the work, and process it asynchronously by calling GetChanges against the list.
GetChanges from your last token and process exactly what changed — even if several edits batched into one notification.
Choosing between them
| Need | Best fit |
|---|---|
| Cancel/validate before save | Synchronous RER (only option) |
| Reliable reaction after change | Webhook (retries + change log) |
| App install/uninstall setup | App-event RER |
| High-volume change processing | Webhook + queue + Azure Function |
I dive into the production-grade webhook architecture — subscription renewal, the storage queue, and idempotent change processing — in a dedicated later article. For now the takeaway is this: use synchronous RERs only where you truly must block a save, and reach for webhooks for everything reactive. They are the bridge from the add-in era to the Microsoft Graph change-notification model that follows.