# How to Build SharePoint Webhooks with Microsoft Graph Subscriptions

A SharePoint webhook is an asynchronous change notification created via Microsoft Graph subscriptions that alerts external applications when document library files change. Setting up notifications requires deploying an HTTPS endpoint, completing the validationToken handshake within 10 seconds, and querying the delta API for changes. Teams feeding files to AI agents can also sync SharePoint folders directly into indexed workspaces.

Source: https://fast.io/resources/sharepoint-webhook/
Author: [Derek Labian](https://fast.io/authors/derek-labian/)
Last reviewed: 2026-10-05

## How SharePoint Webhooks Operate with Microsoft Graph Subscriptions

A webhook endpoint that takes eleven seconds to confirm a subscription handshake causes Microsoft Graph to abort registration immediately and leave the application blind to file changes. Setting up real-time event notifications for a SharePoint document library requires an HTTPS listener that can echo back a validation token within ten seconds, process lightweight change signals without blocking, and reconcile actual file state through differential delta queries.

A SharePoint webhook is an asynchronous change notification created via Microsoft Graph subscriptions that alerts external applications when files in a document library are added, modified, or deleted. In modern Microsoft 365 environments, change notifications represent an event-driven alternative to polling document libraries on fixed intervals. Rather than issuing repeated GET requests against document folders to inspect timestamp headers, engineering teams register a subscription that instructs Microsoft Graph to push HTTP POST notifications whenever content changes.

### Microsoft Graph Change Notifications vs Legacy SharePoint Webhooks

Developers integrating with SharePoint encounter two different webhook mechanisms:

- **Microsoft Graph change notifications API.** The modern standard across Microsoft 365. Subscriptions are created by sending a POST request to `https://graph.microsoft.com/v1.0/subscriptions`. This unified API surface manages notifications across OneDrive drives, SharePoint sites, document libraries, and Microsoft Teams files. Authentication relies on Microsoft Entra ID OAuth 2.0 bearer tokens with application permissions such as `Files.Read.All` or delegated permissions. The official [Microsoft Graph webhook documentation](https://learn.microsoft.com/en-us/graph/change-notifications-delivery-webhooks) outlines endpoint requirements and delivery parameters.
- **Legacy SharePoint REST webhooks.** The older mechanism accessed through SharePoint site collection endpoints at `_api/web/lists('list-id')/subscriptions`. These webhooks require SharePoint-specific app-only principals or classic SharePoint add-in permissions. They lack unified cross-service management, offer fewer resource targets, and require separate endpoint management across every individual site collection.

For new applications and agent integrations, Microsoft Graph change notifications provide tenant-level management and consistent payload schemas.

### The Notification Payload Structure

A common mistake when designing SharePoint webhook consumers is expecting file contents or metadata inside the incoming payload. SharePoint webhooks are lightweight signals. Microsoft Graph sends an alert indicating that a change occurred, not what changed.

A typical webhook payload from Microsoft Graph contains an array of notification objects:

```json
{
  "value": [
    {
      "subscriptionId": "7f8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
      "subscriptionExpirationDateTime": "2026-10-08T18:23:45.1234567Z",
      "changeType": "updated",
      "resource": "drives('b!abc123-def456')/root",
      "resourceData": {
        "@odata.type": "#Microsoft.Graph.DriveItem",
        "@odata.id": "drives('b!abc123-def456')/root",
        "id": "root"
      },
      "clientState": "secret-token-verification-string",
      "tenantId": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d"
    }
  ]
}
```

The payload delivers the subscription identifier, the target resource identifier, the change type, and the optional `clientState` string passed during subscription creation. It does not provide the file name, file size, folder path, or user identity of the person who initiated the change. To determine which file was added or modified, the receiver must query the Microsoft Graph delta API using the resource path provided in the notification.

### Subscribing to Drives vs Lists

When configuring a SharePoint webhook in Microsoft Graph, developers can attach subscriptions to two primary resource types:

- **Drive root container (`drives/{drive-id}/root`).** This targets the document library as a storage drive. It captures file additions, updates, renames, moves, and deletions across all files and subfolders within that library. This is the standard target for file synchronization pipelines.
- **List items (`sites/{site-id}/lists/{list-id}`).** This targets the underlying SharePoint list schema. It tracks list row additions and column metadata updates. While every document library is technically a list in SharePoint architecture, subscribing to the list resource focuses on custom column changes rather than binary file streams.

## Steps to Implement the Four-Stage Webhook Delivery Pipeline

Building a production SharePoint webhook receiver requires completing a sequence of four discrete operational steps:

1. Deploy an HTTPS receiver endpoint with a valid public TLS certificate.
2. Respond to the validationToken handshake during subscription creation within ten seconds.
3. Process incoming lightweight notifications and acknowledge receipt with an immediate HTTP 202 Accepted status.
4. Query the Microsoft Graph /delta endpoint to identify and download changed files.

The interaction sequence flows from initial subscription handshake through event acknowledgment to background reconciliation:

```text
Microsoft Graph                 Webhook Receiver              Background Worker
      |                                |                              |
      | 1. POST ?validationToken=xyz   |                              |
      |------------------------------->|                              |
      |    200 OK (text/plain token)   |                              |
      |<-------------------------------|                              |
      |                                |                              |
      | 2. POST /api/webhook (change)  |                              |
      |------------------------------->|                              |
      |    202 Accepted                | 3. Enqueue event task        |
      |<-------------------------------|----------------------------->|
      |                                |                              |
      | 4. GET /v1.0/drives/{id}/delta |                              |
      |<--------------------------------------------------------------|
      |    Delta changes & new token   |                              |
      |-------------------------------------------------------------->|
```

### Step 1: Deploy an HTTPS Receiver Endpoint

Microsoft Graph delivers notifications exclusively over HTTPS. The destination URL must resolve to a publicly accessible IP address and present a valid TLS certificate issued by a trusted public certificate authority. Self-signed certificates, private internal certificates, and local non-routed addresses fail validation immediately.

During development, engineers frequently expose local development environments using secure tunneling tools before deploying to cloud platforms such as AWS Lambda, Google Cloud Run, or Azure App Service.

### Step 2: Handle the validationToken Handshake

When an application creates a subscription via `POST /v1.0/subscriptions`, Microsoft Graph does not immediately confirm creation in the API response. Instead, it pauses the subscription request and executes a synchronous handshake call against the provided `notificationUrl`.

Microsoft Graph sends an HTTP POST request containing a `validationToken` query parameter in the URL:

```http
POST https://receiver.example.com/api/sharepoint-webhook?validationToken=A1B2C3D4E5F6
Content-Length: 0
```

The webhook endpoint must return the plain-text validationToken parameter within 10 seconds during subscription creation. The response must meet three exact technical conditions:

- **Status code:** HTTP 200 OK.
- **Content-Type header:** `text/plain; charset=utf-8`.
- **Response body:** The URL-decoded string value of the `validationToken` parameter.

If the server returns JSON (such as `{"validationToken": "..."}`), an HTML wrapper, an incorrect status code, or exceeds the 10-second timeout, Microsoft Graph aborts subscription creation and returns an HTTP 400 Bad Request error to the initial API caller.

Here is a minimal Express implementation handling the validation handshake and incoming notifications:

```javascript
import express from 'express';

const app = express();
app.use(express.json());

const EXPECTED_CLIENT_STATE = process.env.GRAPH_CLIENT_STATE;

app.post('/api/sharepoint-webhook', async (req, res) => {
  // 1. Handle validationToken handshake during subscription creation
  if (req.query && req.query.validationToken) {
    const token = decodeURIComponent(req.query.validationToken);
    res.setHeader('Content-Type', 'text/plain');
    return res.status(200).send(token);
  }
  // 2. Validate clientState on incoming change notifications
  const notifications = req.body?.value;
  if (!Array.isArray(notifications)) {
    return res.status(400).send('Invalid payload');
  }
  for (const notification of notifications) {
    if (notification.clientState !== EXPECTED_CLIENT_STATE) {
      console.warn('Received webhook with invalid clientState; dropping.');
      return res.status(403).send('Invalid clientState');
    }
  }
  // 3. Acknowledge delivery immediately to satisfy timeout constraints
  res.status(202).send();
  // 4. Delegate heavy delta processing to background worker
  for (const notification of notifications) {
    await enqueueDeltaSync(notification.resource);
  }
});

async function enqueueDeltaSync(resourcePath) {
  console.log(`Enqueued delta query for: ${resourcePath}`);
}

app.listen(3000, () => {
  console.log('Webhook receiver running on port 3000');
});
```

### Step 3: Process Lightweight Notifications with Fast Acknowledgments

Once the subscription is active, file operations inside the SharePoint document library generate HTTP POST notifications to the receiver URL.

Microsoft Graph change delivery requires timely acknowledgment. If the receiver does not return an HTTP 2xx response within three seconds, Microsoft Graph assumes the delivery failed. It then initiates an automatic retry schedule with exponential backoff for up to four hours. For retried requests, the timeout window extends to ten seconds.

Because downloading files or computing embeddings takes longer than three seconds, the receiver endpoint must never execute heavy file operations synchronously. The endpoint must validate the `clientState`, enqueue the event to a background queue, and return HTTP 202 Accepted or HTTP 200 OK immediately.

### Step 4: Query the Microsoft Graph Delta Endpoint

Because the webhook notification indicates only that a change occurred within the drive container, the worker must query the Microsoft Graph delta API to discover modified files.

The delta API tracks state across calls using a continuation token called a `deltaLink`:

1. **Initial sync:** The worker calls `GET /v1.0/drives/{drive-id}/root/delta`. Microsoft Graph returns pages of drive items. Each page includes an `@odata.nextLink` until the final page, which returns an `@odata.deltaLink` containing a state token.
2. **Subsequent webhook triggers:** When a webhook arrives, the worker calls the stored `@odata.deltaLink`. Microsoft Graph returns only the items added, modified, or deleted since that token was generated.
3. **Persisting the delta token:** After processing the changed items, the worker saves the new `@odata.deltaLink` to persistent storage for the next webhook event.

```http
GET https://graph.microsoft.com/v1.0/drives/b!abc123-def456/root/delta?token=a1b2c3d4e5f6
Authorization: Bearer eyJ0eXAi...
```

The response returns individual item objects with details including file names, MIME types, hashes, and a `@odata.type` field. If an item was deleted, the response includes a `"deleted": {}` facet.

## Why Microsoft Graph Subscriptions Require Proactive Renewal Management

Microsoft Graph webhook subscriptions are temporary by design. Microsoft enforces finite expiration windows to prevent abandoned listeners from consuming cloud resources across tenants indefinitely.

SharePoint driveItem subscriptions expire after a maximum of 4,230 minutes (under 3 days), requiring scheduled renewal. While directory-level subscriptions like user accounts can persist for up to 29 days, file and drive subscriptions cap out at 4,230 minutes (approximately 2.94 days). Official limits are listed in the [Microsoft Graph subscription reference](https://learn.microsoft.com/en-us/graph/api/resources/subscription?view=graph-rest-1.0).

### Automated Subscription Renewal

To maintain continuous change tracking without gaps, applications must implement an automated renewal worker that updates the subscription before it reaches its expiration date.

A renewal request issues an HTTP PATCH call to the subscription resource with an updated `expirationDateTime`:

```http
PATCH https://graph.microsoft.com/v1.0/subscriptions/7f8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d
Content-Type: application/json
Authorization: Bearer eyJ0eXAi...

{
  "expirationDateTime": "2026-10-08T12:00:00.0000000Z"
}
```

```javascript
import axios from 'axios';

async function renewSubscription(subscriptionId, accessToken) {
  // Set new expiration to 4200 minutes from now (safely within 4230 minute limit)
  const expiration = new Date(Date.now() + 4200 * 60 * 1000).toISOString();
  try {
    const response = await axios.patch(
      `https://graph.microsoft.com/v1.0/subscriptions/${subscriptionId}`,
      { expirationDateTime: expiration },
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
          'Content-Type': 'application/json',
        },
      }
    );
    console.log(`Renewed subscription ${subscriptionId} until ${response.data.expirationDateTime}`);
    return response.data;
  } catch (error) {
    console.error('Failed to renew subscription:', error.response?.data || error.message);
    throw error;
  }
}
```

A common pattern is running a scheduled job every 24 to 36 hours. If a renewal fails due to network partitions or expired client credentials, the application has sufficient runway to retry before the 4,230-minute window closes.

### Handling Expired Subscriptions and State Loss

If a subscription expires, Microsoft Graph deletes it permanently. It cannot be reactivated with a PATCH call. When this happens:

- Incoming file modifications cease triggering notifications.
- The application must issue a new `POST /v1.0/subscriptions` request, initiating another `validationToken` handshake.
- The stored `deltaLink` may become stale if the time between expiration and resubscription exceeds Microsoft Graph delta token retention limits.
- If the delta token expires, the application must perform a complete library re-index to re-synchronize state.

### Subscribing to Lifecycle Notifications

To guard against silent subscription loss, Microsoft Graph supports lifecycle notifications. When creating a subscription, you can specify a `lifecycleNotificationUrl`:

```json
{
  "changeType": "updated",
  "notificationUrl": "https://receiver.example.com/api/sharepoint-webhook",
  "resource": "drives/b!abc123-def456/root",
  "expirationDateTime": "2026-10-08T12:00:00.0000000Z",
  "clientState": "secret-token-verification-string",
  "lifecycleNotificationUrl": "https://receiver.example.com/api/sharepoint-lifecycle"
}
```

The lifecycle URL must pass the same 10-second `validationToken` handshake during creation. Once registered, Microsoft Graph delivers lifecycle events including:

- `reauthorizationRequired`: Sent when tenant access policies, permissions, or administrative consent change, warning that the subscription will stop unless reauthorized.
- `missed`: Sent when Microsoft Graph attempted to deliver notifications but the receiver endpoint was unreachable or returned errors. The receiver can use this signal to trigger an immediate delta sync.
- `subscriptionRemoved`: Sent if an administrator or policy removes the subscription.

## Operational Bottlenecks of Running Custom Webhook Listeners

Building and operating a custom SharePoint webhook pipeline introduces significant infrastructure overhead. While a basic listener script runs in a few lines of code, running reliable webhook consumers in production requires maintaining an entire supporting architecture.

### Ingress Infrastructure and Security Maintenance

Because Microsoft Graph requires publicly addressable HTTPS endpoints, operations teams must configure and maintain:

- **Public domain names and DNS records.** Every webhook receiver requires public DNS routing.
- **TLS certificate lifecycle management.** An expired certificate breaks the validation handshake and halts all notification delivery across the organization.
- **Ingress firewalls and IP filtering.** Microsoft publishes IP ranges for Microsoft 365 services, but these ranges rotate frequently. Maintaining restrictive firewall rules requires subscribing to Microsoft's endpoint notification feeds or terminating traffic at an API gateway.
- **Secret rotation.** The Entra ID client secrets and application certificates used to authenticate with Microsoft Graph expire periodically. When credentials lapse, the delta sync worker cannot retrieve files even if notifications arrive successfully.

### Event Storms and Throttling Limits

SharePoint document libraries experience extreme burst activity. When an employee uploads a zip archive containing thousands of engineering schematics, imports a folder hierarchy, or executes a bulk document tagging script, Microsoft Graph dispatches hundreds of webhook notifications in rapid succession.

This burst creates two operational failure modes:

1. **Queue saturation.** If the receiver enqueues events without debouncing, the worker processes identical drive containers repeatedly, wasting compute and memory.
2. **Microsoft Graph 429 throttling.** If worker tasks immediately trigger concurrent delta queries against the same drive, Microsoft Graph issues HTTP 429 (Too Many Requests) responses with `Retry-After` headers. The pipeline must implement backoff and token bucket rate limiters to avoid tenant-wide throttling.

### The Impedance Mismatch for AI Agents

For autonomous AI agents, coding assistants, and automated knowledge pipelines, maintaining custom webhook infrastructure presents a fundamental architectural mismatch.

AI agents are computational consumers. They require clean file context, searchable vector representations, and semantic retrieval to answer technical questions, audit records, or draft briefs. Forcing an AI system to run background web servers, manage port forwarding, parse raw delta payloads, and execute token renewal cron jobs adds brittle scaffolding around the core AI task. When an agent restarts, local state and webhook listener processes disappear.

## How to Connect SharePoint to AI Workspaces Without Webhook Overhead

Engineering teams looking to supply SharePoint files to AI agents do not need to build custom webhook receivers, delta consumers, and renewal crons. Instead, teams can keep their existing storage in SharePoint and sync relevant document libraries into an intelligent workspace.

In this architecture, your organization preserves SharePoint as the corporate system of record. Permissions, intranet links, and employee collaboration continue uninterrupted. Instead of maintaining an event pipeline, you connect the SharePoint document library using [Fast.io Cloud Sync](/product/cloud-sync/).

### Scheduled Cloud Synchronization

Cloud Sync bridges external enterprise storage systems into shared workspaces. Fast.io provides Cloud Sync for Google Drive, Dropbox, Box, and OneDrive (connecting SharePoint document libraries through the OneDrive connector).

Sync operations can be configured one-way or two-way, on a schedule or on demand. Synchronization is never continuous, live, or real-time. By configuring a scheduled synchronization interval, the workspace checks the SharePoint library for modified files and pulls updates into the workspace automatically. This completely removes the need for public HTTPS ingress endpoints, 10-second validation handshakes, and renewal scripts.

### Automatic Indexing in Intelligence Mode

Once files arrive in a workspace, Fast.io's Intelligence Mode indexes them automatically. Rather than building a separate vector database, embedding pipeline, and chunking system, files are processed on arrival:

- **Hybrid search.** The workspace combines full-text indexing, semantic retrieval, and search-by-metadata-value into a unified search endpoint.
- **Metadata Views.** Turn unstructured documents into queryable tables. Users describe target fields in natural language, and the system extracts structured schemas across PDFs, Word documents, spreadsheets, and scanned records without manual OCR templates. Detailed schema extraction is documented on the [document data extraction product page](/product/document-data-extraction/).
- **Collaborative Notes.** Team members and AI agents can write shared notes and synthesize SharePoint documents in the workspace.

### Accessing Files via Remote MCP

For developers building agent workflows with Claude Code, Cursor, OpenClaw, or custom LLM frameworks, [Fast.io for agents](/storage-for-agents/) exposes a consolidated MCP toolset. The remote MCP server runs over Streamable HTTP: coding agents such as Claude Code and Cursor connect at `https://mcp.fast.io/mcp/code`, and Claude apps and general MCP clients connect at `https://mcp.fast.io/mcp/tools`. Headless code sends an API key in the authorization header, as in this headless configuration:

```json
{
  "mcpServers": {
    "fastio": {
      "url": "https://mcp.fast.io/mcp/code",
      "headers": {
        "Authorization": "Bearer YOUR_FASTIO_API_KEY"
      }
    }
  }
}
```

Through the MCP server, an agent searches indexed files, inspects version history, and queries document content with cited sources. Instead of downloading whole SharePoint document libraries across the network or parsing low-level delta links, the agent issues semantic queries and retrieves precise excerpts.

The connector comparison is published at https://fast.io/benchmarks/, where Fastio was measured the fastest and the lowest cost of the providers tested.

### Team Governance and Plans

Fast.io provides shared workspaces backed by per-file version history, access granted per organization, workspace, and share through member roles, and a detailed activity log.

Monthly plans start with a 14-day free trial that requires a credit card. Organizations can explore plan options on the [pricing page](/pricing/) and start syncing SharePoint folders into indexed workspaces immediately. Teams preserve their existing SharePoint storage, synchronize on schedule, and provide their AI agents with an indexed, searchable workspace.

## Frequently asked questions

### How do SharePoint webhooks work with Microsoft Graph?

SharePoint webhooks work through the Microsoft Graph change notifications API. An application subscribes to a target resource, such as a document library drive root, by sending a POST request to /v1.0/subscriptions. When files within that library are created, modified, or deleted, Microsoft Graph pushes an HTTP POST notification containing the subscription ID and resource path to the configured HTTPS endpoint. Because the webhook is a lightweight ping that contains no file content, the receiving application queries the Microsoft Graph /delta endpoint to discover the specific changes.

### How do I handle the SharePoint webhook validationToken handshake?

When creating a subscription, Microsoft Graph sends an HTTP POST request to your notification URL with a validationToken query parameter. The endpoint must decode the validationToken string and return it as plain text with an HTTP 200 OK status code and a Content-Type of text/plain within 10 seconds. If the endpoint returns JSON, HTML, an error code, or exceeds the 10-second timeout, Microsoft Graph cancels the subscription creation.

### What is the maximum expiration time for a SharePoint webhook subscription?

SharePoint driveItem subscriptions in Microsoft Graph expire after a maximum of 4,230 minutes (under 3 days). To keep the webhook active, the subscribing application must send an HTTP PATCH request to /v1.0/subscriptions/{subscription-id} with a refreshed expirationDateTime before the current subscription expires. If a subscription reaches its expiration time without renewal, Microsoft Graph permanently deletes it.

### What happens when Microsoft Graph fails to deliver a SharePoint webhook notification?

If the webhook endpoint does not return an HTTP 2xx response within 3 seconds, Microsoft Graph treats the attempt as a delivery failure. It retries delivery with exponential backoff for up to 4 hours, extending the request timeout to 10 seconds on retries. If the endpoint remains unreachable or returns persistent 5xx errors, Microsoft Graph drops the notification. Applications can monitor lifecycle notifications to detect dropped messages and execute a manual delta sync.

### How do I retrieve changed files after receiving a SharePoint webhook ping?

To retrieve changed files, call the Microsoft Graph delta endpoint for the drive resource: GET /v1.0/drives/{drive-id}/root/delta. On the first run, the API returns pages of drive items ending in an @odata.deltaLink containing a state token. When subsequent webhook pings arrive, the application queries that stored deltaLink to fetch only the items modified, added, or deleted since the previous request.

## Sources

- [Microsoft Learn: Receive change notifications through webhooks](https://learn.microsoft.com/en-us/graph/change-notifications-delivery-webhooks): The client must respond with the URL-decoded validation token within 10 seconds of receiving the subscription validation request.

## About Fast.io

Fast.io provides shared workspaces where people and AI agents work on the same files, with built-in semantic search and citation-backed chat over what they hold. Agents reach it through a remote MCP server, a REST API at https://api.fast.io/current/, and a command line client published on npm as @vividengine/fastio-cli. MCP setup is at https://mcp.fast.io/docs: Claude and most MCP clients connect to https://mcp.fast.io/mcp/tools, ChatGPT to https://mcp.fast.io/mcp/operations, and coding agents to https://mcp.fast.io/mcp/code.
