Skip to content

Repository files navigation

graph-webhook

A Go library for receiving Microsoft Graph change notifications safely: the validation-token handshake, constant-time clientState verification, and a subscription lifecycle manager that keeps your webhook subscriptions alive. You get the decoded change; what to do with it is your callback.

Extracted and generalized from a production Microsoft 365 mail integration.

Why it's more than an http.HandlerFunc

Graph webhooks have sharp edges this handles for you:

  • Validation handshake — on subscription setup Graph calls your endpoint with a validationToken; the handler echoes it back with the exact text/plain, nosniff, 512-byte-cap response Graph requires.
  • Authenticity — every notification carries the clientState you set at subscribe time; the handler verifies it with crypto/subtle.ConstantTimeCompare (per-subscription via your store, or a shared secret) and refuses to construct with no secret at all, so an empty value can't silently match.
  • Doesn't lose work — a handler-callback error answers 503 so Graph redelivers; accepted work is detached with context.WithoutCancel so a client hang-up can't drop it. Body and batch sizes are capped (1 MB / 100 items).
  • Keeps subscriptions alive — a Manager creates, renews-before-expiry, and deletes subscriptions through a Subscriber interface you implement over your own authenticated Graph client.
  • Zero dependencies — standard library only.

Usage

import graphwebhook "github.com/biglill/graph-webhook"

h, err := graphwebhook.New(graphwebhook.Config{
    ClientState: myClientStateSecret,          // or back it per-subscription with a SubscriptionStore
    OnChange: func(ctx context.Context, ch graphwebhook.Change) error {
        log.Printf("change: %s on %s", ch.ChangeType, ch.Resource)
        return handle(ctx, ch)                 // returning an error ⇒ 503, Graph redelivers
    },
})
if err != nil { log.Fatal(err) }
http.Handle("/webhooks/graph", h)

// Keep subscriptions renewed:
mgr := graphwebhook.NewManager(mySubscriber, mySubStore, graphwebhook.ManagerConfig{
    NotificationURL: "https://your.app/webhooks/graph",
    Resource:        "me/mailFolders('Inbox')/messages",
    ChangeType:      "created",
    Lifetime:        60 * time.Minute,
    RenewWindow:     10 * time.Minute,
})
mgr.RenewExpiring(ctx) // call on a timer

The seams you implement:

type OnChangeFunc func(ctx context.Context, ch Change) error
type Subscriber interface { CreateSubscription(...); RenewSubscription(...); DeleteSubscription(...) }
type SubscriptionStore interface { Get(...); Save(...); ListExpiring(...); Delete(...) }

Testing

go test ./...        # validation handshake, clientState mismatch/skip, batch caps, renewal
go test -race ./...

License

MIT — see LICENSE.

About

Go library for receiving Microsoft Graph change notifications — validation handshake, constant-time clientState verification, and a subscription-renewal manager. Zero deps.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages