Guide to Microsoft SharePoint Webhooks: Features and Best Practices
Microsoft SharePoint webhooks notify your systems when items change in a list or document library. If you're building on SharePoint, webhooks are how you learn a list changed without polling, though you then call the API to find out what changed.
This guide covers how SharePoint webhooks work, the validation handshake (there's no signature), the thin notifications and the GetChanges pattern, subscription renewal, and the best practices for production.
What are Microsoft SharePoint webhooks?
SharePoint list webhooks send an HTTP POST to a notificationUrl you register per list. There's no HMAC or request signature. Authenticity rests on two things: a mandatory validationtoken echo when the subscription is created, and an optional clientState string echoed in every notification.
Notifications are thin, they tell you a list changed but carry no change details, so you call the list's GetChanges API with a stored change token to learn what happened.
Microsoft SharePoint webhook features
| Feature | Details |
|---|---|
| Configuration | POST /_api/web/lists('{list-guid}')/subscriptions |
| Validation | Echo the validationtoken query param as text/plain 200 within a few seconds |
| Per-message check | Optional clientState echoed in notifications (shared-secret sanity check) |
| Signature | None |
| Payload | Thin, batched under value[]: subscriptionId, clientState, resource (list GUID), ... no change detail |
| Learn what changed | Call GetChanges with a stored change token |
| Expiry | Max 180 days; must be renewed via PATCH |
| Retries | 5x at 5-minute intervals on non-2xx |
| SDK | @pnp/sp (manages subscriptions, not verification) |
Common events
Notifications do not carry an event type. The underlying list events are:
| Event | Fires when |
|---|---|
ItemAdded | A list item / document is added |
ItemUpdated | A list item / document is updated |
ItemDeleted | A list item / document is deleted |
Because the notification only says "this list changed", you determine the specific change by calling GetChanges.
Setting up Microsoft SharePoint webhooks
Create a subscription with POST /_api/web/lists('{list-guid}')/subscriptions, providing resource, notificationUrl, expirationDateTime (max 180 days out), and an optional clientState. SharePoint validates the endpoint with the handshake below before creating the subscription. Renew via PATCH before expiry, SharePoint doesn't track usage, so an un-renewed subscription simply lapses.
Securing Microsoft SharePoint webhooks
There's no signature, so verification is two steps.
The validation handshake
When a subscription is created (or its notificationUrl changes), SharePoint POSTs with a validationtoken query-string parameter. Echo that exact token back as an HTTP 200 text/plain body within a few seconds, or creation fails.
clientState on notifications
Set a clientState at subscription time; SharePoint echoes it in every notification's clientState field. Compare it to your stored value as a shared-secret sanity check.
const CLIENT_STATE = process.env.SP_CLIENT_STATE;
app.post("/webhook", express.json(), (req, res) => {
// Validation handshake: echo the token as text/plain
const validationToken = req.query.validationtoken;
if (validationToken) {
return res.status(200).type("text/plain").send(validationToken);
}
// Notifications arrive batched under value[]
const notifications = (req.body && req.body.value) || [];
for (const n of notifications) {
if (CLIENT_STATE && n.clientState !== CLIENT_STATE) {
return res.sendStatus(400); // not from your subscription
}
}
res.sendStatus(200); // acknowledge fast (SharePoint retries 5x on non-2xx)
for (const n of notifications) {
// Enqueue: call GetChanges on n.resource with your stored change token
processQueue.add(n);
}
});
Because there's no cryptographic signature, set clientState, keep the notificationUrl secret, and use HTTPS.
Microsoft SharePoint webhook limitations and pain points
No signature, only clientState
The Problem: There's no HMAC to verify. The only per-message identity signal is the optional clientState. Without it, any POST to your URL looks legitimate.
Why It Happens: SharePoint's model is a validation handshake plus an optional shared-state string, not payload signing.
Workarounds:
- Set
clientStateand check it, keep the URL secret, and use HTTPS.
How Hookdeck Can Help: Hookdeck gives you a dedicated ingestion URL and can enforce its own verification in front of your endpoint, adding a layer beyond clientState.
Thin notifications require GetChanges
The Problem: Notifications carry no change detail, just that a list changed. You must call GetChanges with a stored change token to learn what happened, and manage that token per list.
Why It Happens: SharePoint notifies that something changed and expects you to pull the delta.
Workarounds:
- Store a change token per list, call GetChanges on notification, and advance the token.
How Hookdeck Can Help: Hookdeck durably queues each notification so your worker calls GetChanges at a controlled rate, without racing the 5-minute retry cadence.
Subscriptions expire at 180 days
The Problem: A subscription expires at its expirationDateTime (max 180 days) and must be renewed via PATCH. Miss the renewal and notifications stop silently.
Why It Happens: SharePoint time-boxes subscriptions and doesn't auto-renew.
Workarounds:
- Track
expirationDateTimeand renew well before it, from a scheduled job.
How Hookdeck Can Help: Hookdeck's observability surfaces a drop in notification volume, so a lapsed subscription shows up as an alert rather than silent data loss.
The validation window and retries
The Problem: You must echo the validationtoken within a few seconds or creation fails, and notifications retry only 5x at 5-minute intervals, so a longer outage drops them.
Why It Happens: SharePoint validates quickly and retries sparingly.
Workarounds:
- Echo the token immediately, acknowledge fast, and reconcile via GetChanges for anything missed.
How Hookdeck Can Help: Hookdeck can complete the handshake and durably queue notifications, so a downstream hiccup doesn't exhaust the small retry budget.
Best practices
Echo the validationtoken and check clientState
Return the validationtoken as text/plain 200 within a few seconds, and verify clientState on every notification.
Drive off GetChanges with a stored token
Treat notifications as a nudge; call GetChanges with a per-list change token to learn and process the actual changes.
Renew before 180 days
Track expirationDateTime and renew via PATCH ahead of expiry.
Acknowledge fast, process asynchronously
Return 200 quickly and defer GetChanges to a queue. See why to process webhooks asynchronously.
Make Microsoft SharePoint webhooks production-ready
Hookdeck completes the handshake, adds verification, and durably queues every list notification
Conclusion
SharePoint list webhooks have no signature: authenticity is the validationtoken handshake plus an optional clientState, and notifications are thin, so you call GetChanges with a stored change token to learn what changed. Set clientState, drive off GetChanges, renew before the 180-day expiry, and acknowledge fast.
Hookdeck completes the handshake, adds a verification layer SharePoint lacks, and durably queues every notification at the edge, so your app processes trustworthy events without losing any to a lapsed subscription.
Get started with Hookdeck for free and handle Microsoft SharePoint webhooks reliably in minutes.