Preparing webhook to receive notifications
Schema
We use CloudEvents v1.0 schema to send notifications
Example event
{
"id": "9aeb0fdf-c01e-0131-0922-9eb54906e209",
"type": "ConsumptionsAvailable",
"source": "/notification-service",
"data": { "deviceId": "eb093330-f5bc-4c06-82e5-3cf671d3412e" },
"time": "2021-11-05T12:51:19.8506671+00:00",
"specversion": "1.0",
"trackingid": "ea626280-b8b7-4636-a5bb-0c913d5905b2",
"datacontenttype": "application/json"
}
Endpoint requirements
The Handshake
The endpoint should support Abuse Protection - when assigning the webhook, the HTTP OPTIONS request will be sent to the provided endpoint.
When the request is received there are a few ways to complete handshake:
Synchronous handshake
Request will contain Webhook-Request-Origin header
Take the header value and put it under WebHook-Allowed-Origin header for a response
Return response with 200 OK status code
Asynchronous handshake
Request will contain WebHook-Request-Callback header with an URL
The delivery target grants permission by issuing an HTTPS GET or POST request against the given URL. The HTTP GET request can be performed manually using a browser client.
Authentication
Requests to provided endpoint will include the header tenant provides
Synchronous handshake implementation with Azure functions
Notification processing (handle POST request)
public async Task<IActionResult> ProcessNotifications(
[HttpTrigger(AuthorizationLevel.Anonymous, nameof(HttpMethods.Post), Route = Route)] HttpRequest req,
CancellationToken cancellationToken)
{
if (!req.Headers.TryGetValue("some-auth-header", out var authHeaderValue) || !authHeaderValue.Equals(expectedValue))
{
return new ForbidResult();
}
var cloudEvents = Azure.Messaging.CloudEvent.ParseMany(await BinaryData.FromStreamAsync(req.Body, cancellationToken));
foreach (var cloudEvent in cloudEvents)
{
await HandleCloudEvent(cloudEvent, cancellationToken);
}
return new AcceptedResult();
}
Handshake (handle OPTIONS request)
public IActionResult RequestNotificationsDelivery(
[HttpTrigger(AuthorizationLevel.Anonymous, nameof(HttpMethods.Options), Route = Route)] HttpRequest req)
{
if (req.Headers.TryGetValue("some-auth-header", out var authHeaderValue)
&& authHeaderValue.Equals(expectedValue)
&& req.Headers.TryGetValue("WebHook-Request-Origin", out var requestOriginValue)
&& requestOriginValue.Equals("eventgrid.azure.net"))
{
req.HttpContext.Response.Headers.Add("WebHook-Allowed-Origin", requestOriginValue);
return new OkResult();
}
return new ForbidResult();
}