GraphApi.Webhook (GraphApi v1.0.0-rc.1)

Copy Markdown View Source

Helpers for handling Microsoft Graph webhook notifications.

When you create a subscription, Microsoft Graph first sends a validation request to confirm your endpoint. After that, it sends notification requests whenever the subscribed resource changes.

Validation Flow

Microsoft sends a POST with ?validationToken=<token> as a query parameter. Your endpoint must respond with 200 OK and the token as plain text.

Notification Flow

Microsoft sends a POST with a JSON body containing an array of notifications under the "value" key. Your endpoint must respond with 202 Accepted within 3 seconds.

Example (Plug/Phoenix)

def webhook(conn, params) do
  case GraphApi.Webhook.classify(conn) do
    {:validate, token} ->
      conn
      |> put_resp_content_type("text/plain")
      |> send_resp(200, token)

    :notification ->
      notifications = GraphApi.Webhook.parse_notifications(conn.body_params)

      for n <- notifications do
        # Process asynchronously to respond within 3 seconds
        MyApp.NotificationWorker.enqueue(n)
      end

      send_resp(conn, 202, "")
  end
end

Summary

Functions

Classifies an incoming webhook request.

Parses notifications from a webhook request body.

Validates the clientState of a notification against an expected value.

Functions

classify(conn)

@spec classify(Plug.Conn.t() | map()) :: {:validate, String.t()} | :notification

Classifies an incoming webhook request.

Returns {:validate, token} if this is a subscription validation request, or :notification if this is a change notification.

Parameters

  • conn_or_params — A Plug.Conn struct or a params map with string keys

Examples

case Webhook.classify(conn) do
  {:validate, token} -> send_resp(conn, 200, token)
  :notification -> process_and_respond_202(conn)
end

parse_notifications(arg1)

@spec parse_notifications(map()) :: [map()]

Parses notifications from a webhook request body.

Returns a list of notification maps, each containing:

  • "subscriptionId" — The subscription that triggered this notification
  • "changeType""created", "updated", or "deleted"
  • "resource" — The resource path that changed (e.g., "users/user-id")
  • "resourceData" — Additional data about the resource (if included)
  • "clientState" — The client state string from the subscription (for validation)

Parameters

  • body — The parsed JSON body (map with string keys)

Examples

notifications = Webhook.parse_notifications(conn.body_params)
# => [%{"subscriptionId" => "...", "changeType" => "updated", "resource" => "users/abc", ...}]

valid_client_state?(arg1, expected)

@spec valid_client_state?(map(), String.t()) :: boolean()

Validates the clientState of a notification against an expected value.

Returns true if the notification's clientState matches the expected value. This helps verify that notifications are genuinely from Microsoft Graph and not spoofed.

Examples

for notification <- Webhook.parse_notifications(body) do
  if Webhook.valid_client_state?(notification, "my-secret") do
    process(notification)
  else
    Logger.warning("Invalid clientState, ignoring notification")
  end
end