# Microsoft SharePoint Webhooks

## When to Use This Skill

* Setting up Microsoft SharePoint list or document-library webhook handlers
* Completing the SharePoint `validationtoken` subscription handshake
* Validating the `clientState` shared secret on incoming notifications
* Understanding the thin SharePoint notification payload
* Reacting to list-item changes (ItemAdded, ItemUpdated, ItemDeleted) via the GetChanges API

## How SharePoint Webhooks Differ

SharePoint webhooks are not HMAC-signed and are not Standard Webhooks. There is no request signature. Authenticity relies on two things instead:

1. Validation handshake — when a subscription is created (or its `notificationUrl` changes), SharePoint POSTs with a `validationtoken` query-string parameter. Your endpoint must echo that exact token back as an HTTP `200` `text/plain` body within ~5 seconds, or the subscription is never created.
2. `clientState` — an opaque string you set at subscription time. SharePoint echoes it in the `clientState` field of every notification. Compare it to your stored secret as a shared-secret sanity check. It is the only per-message identity signal (not a signature).

Notifications are thin and batched under a `value` array and carry no change details — you call the list [GetChanges API](https://learn.microsoft.com/en-us/sharepoint/dev/apis/webhooks/lists/overview-sharepoint-list-webhooks) with a stored change token to learn what actually changed.

## Verification (core)

```javascript
const crypto = require('crypto');

// 1. Validation handshake — runs BEFORE any body parsing.
//    Echo the validationtoken query param back verbatim as text/plain.
const token = new URL(req.url, 'http://localhost').searchParams.get('validationtoken');
if (token) {
  res.setHeader('Content-Type', 'text/plain');
  return res.status(200).send(token);   // must reply within ~5s or creation fails
}

// 2. clientState — timing-safe compare the shared secret on every notification.
function clientStateMatches(received, expected) {
  if (typeof received !== 'string' || typeof expected !== 'string') return false;
  const a = Buffer.from(received);
  const b = Buffer.from(expected);
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

const { value = [] } = JSON.parse(rawBody);   // thin, batched notifications
const ok = value.every(n => clientStateMatches(n.clientState, process.env.SHAREPOINT_CLIENT_STATE));
if (!ok) return res.status(400).send('Invalid clientState');
// Notifications carry no change details — call list GetChanges with your stored change token.

```

> For complete handlers with route wiring, GetChanges follow-up, and tests, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/microsoft-sharepoint-webhooks/examples/express/)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/microsoft-sharepoint-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/microsoft-sharepoint-webhooks/examples/fastapi/)

## Notification Payload

Each notification in the batch has this shape (no change details):

```json
{
  "value": [
    {
      "subscriptionId": "91779246-afe9-4525-b122-6c199ae89211",
      "clientState": "your-opaque-secret",
      "expirationDateTime": "2016-04-30T17:27:00.0000000Z",
      "resource": "b9f6f714-9df8-470b-b22e-653855e1c181",
      "tenantId": "00000000-0000-0000-0000-000000000000",
      "siteUrl": "/",
      "webId": "dbc5a806-e4d4-46e5-951c-6344d70b62fa"
    }
  ]
}

```

`resource` is the list GUID. To learn what changed, call GetChanges on that list.

## Change Types (from GetChanges)

The notification does not carry the event type. After a notification you call GetChanges and inspect each change's `ChangeType`:

| ChangeType | List event | Triggered when |
| --- | --- | --- |
| `Add` | ItemAdded | An item or file is created |
| `Update` | ItemUpdated | An item or file is modified |
| `DeleteObject` | ItemDeleted | An item or file is deleted |
| `Rename` | ItemRenamed | An item or file is renamed |
| `Restore` | ItemRestored | An item is restored from the recycle bin |
| `MoveAway` | ItemMovedOut | An item or file is moved out of the location |
| `MoveInto` | ItemMovedInto | An item or file is moved into the location |

> For the full change reference, see [SharePoint list webhooks](https://learn.microsoft.com/en-us/sharepoint/dev/apis/webhooks/lists/overview-sharepoint-list-webhooks).

## Environment Variables

```bash
SHAREPOINT_CLIENT_STATE=your-opaque-secret   # clientState set at subscription time

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 microsoft-sharepoint --path /webhooks/microsoft-sharepoint

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/microsoft-sharepoint-webhooks/references/overview.md) - SharePoint webhook concepts, change types
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/microsoft-sharepoint-webhooks/references/setup.md) - Creating and renewing subscriptions
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/microsoft-sharepoint-webhooks/references/verification.md) - Handshake and clientState details