GitHub can send our apps a webhook message for a variety of events that may happen on a repo such as a repo push, a fork or an issue being created. These messages are HTTP POST requests and can be easily handled by HTTP triggered Azure Functions.
In this post, we’ll look at using Azure Functions to handle a Git push event webhook in particular, but the process is very similar for other kind of events too.
Create a webhook on a repo
We’ll start by creating a webhook and making a commit on GitHub so we can inspect the incoming request headers and payload.
From our repository’s Settings → Webhooks menu, we’ll create a new webhook and set the highlighted items below. For now, we can use any value for the Payload URL.

Next, we’ll commit and push a test change to our repository.
At this point GitHub should try to send a HTTP POST request to whatever URL we entered above to notify it of a new push event.
Navigate to the Recent Deliveries tab on the Manage webhook page to view the delivery attempt. Clicking on the delivery provides detailed information, as shown below:

The webhook will have received a 502 response since the URL doesn’t exist yet, which is expected. Our primary interest at this stage is understanding the webhook’s request format.
The highlighted x-Hub-Signature-256 header contains a SHA256 signature, included because we specified a secret earlier. This header enables our Azure Function to verify that the webhook requests are genuinely from GitHub and have not been tampered with.
Also highlighted above is the body of the webhook request. We can copy this JSON body and paste it into ChatGPT, json2csharp.com or use Visual Studios built in ‘Paste JSON As Classes’ functionality to create a strongly typed class or record hierarchy to store the response body.
Finally note the Redeliver button above. This is a super useful feature of the GitHub webhook setup and allows us to simply resend webhooks without having to do the source event again.
Create an Azure Function to process and validate webhook payload
The code below is an example of how our Azure Function to handle GitHub webhooks push events might look.
Click on the image for a larger view in a new window.
What the Azure Function does …
- Extract Signature Header:
- Checks for the presence of the X-Hub-Signature-256 header.
- Returns a 401 Unauthorized response if the header is missing.
- Read Request Body:
- Reads the raw JSON payload from the HTTP request body.
- Compute Hash:
- Creates a SHA-256 HMAC hash of the request body using the shared secret.
- Compare Hashes:
- Compares the computed hash with the value in the X-Hub-Signature-256 header.
- Returns a 401 Unauthorized response if the computed hash and the X-Hub-Signature-256 value don’t match.
- Deserialize Payload:
- Parses the request body into the GitHubPushPayload object.
- Handles invalid JSON gracefully by returning a 400 Bad Request.
- Log Repository and Commit Details:
- Logs the repository name and iterates through the commits array to log detailed commit information.
- Return Response:
- Returns an OK response with the message “Webhook received and processed.” if all steps succeed.
Note, the shared secret in step 3 is hardcoded for the purposes of this example. It needs to match whatever the secret value we have input into the GitHub webhook setup page is and in practice should come from a secure location like Azure Key Vault.
The strongly typed record hierarchy GitHubPushPayload is a slimmed down C# version of the push event request JSON. The specific format of the class or record hierarchy we need is dependent on what kind of webhook event we are processing. If we need to check what type of event triggered a function we can examine the X-GitHub-Event header.
Testing webhook integration with a locally running Azure Function using Visual Studio Dev Tunnels
To actually test the integration between GH and our locally running AZ Function we will use Visual Studio Dev Tunnels which allow us to expose (and debug) our localhost APIs via remote URLs.
After we create a Dev Tunnel we will have a remote URL pointer into our running localhost instance. We can then put this URL into the Payload URL field in the GitHub webhook setup page as shown earlier and have our localhost breakpoints fired when the remote URL is hit.
The first step here is to enable Dev Tunnels in the launchsettings.json file of our Azure Function project:

Note, launchBrowser above is an optional and convenience setting.
Next we will create a new tunnel from the View -> Other Windows -> Dev Tunnels screen as shown below. Note the settings chosen below.
Please read up on the security implications of the different settings.

Now run the application.
We will see the Dev Tunnel root page open up in our browser, note the address in the address bar. This is our Dev Tunnel URL which we’ll need to input into GitHub.
The first time we access a particular tunnel it will present us with a dialog similar to below which we can click Continue on.

Input the AZ Functions full Dev Tunnel address into the Payload URL on the GH Manage webhook screen
Now we go back to GitHub and input the full URL pointer into the HandleRepoPush function we made earlier. Its format will be DEV_TUNNEL_URL/api/HandleRepoPush and will be similar to below:
After we have clicked Update webhook we can go back into the failed webhook from earlier and click Redeliver.

Assuming the app is running and everything goes well we should see a console screen similar to below which outputs details about commits which have been pushed.
Click on the image for a larger view in a new window.
And on the GitHub side we should now see the webhook got a 200 Success response and a body of ‘Webhook received and processed.‘ from our Azure Function.

Inspecting all traffic to a Dev Tunnel
Note, there is also a Developer Tools like experience for monitoring all incoming traffic into a Dev Tunnel. We can get to it by clicking on View in the Tunnel URL column on the Dev Tunnel page (when the app is running) and then clicking Inspect.


Integrating a GitHub webhook with a deployed Azure Function
Once we’ve validated functionality using Dev Tunnels and our local environment all that’s left is to deploy to Azure and then update the Payload URL field in GitHub again to the correct URL.
After deploying the function to Azure we can get the full URL including access code from the ‘Code + Test’ -> ‘Get Function URL‘ option on the main screen for our function. The URL we need is the default (Function key) one highlighted on the right below and will look a bit like this:
https://someFunctionAppAddress.azurewebsites.net/api/HandleRepoPush?code=123456123456123456123456123456123456123456
Click on the image for a larger view in a new window.
After pasting in the URL from above into the webhook Payload URL field and saving, we’ll hit Redeliver on one of the requests from earlier. Hopefully we’ll see a 200 come back from an AZ function URL like below:

and after some short time we’ll begin to see all our invocations listed:

Summary
That’s the process for a push event, but the process is very very similar for the other event types too. The main thing that will differ from event type to event type is the format of the webhook request payload.
Main steps to recap again are:
- Create webhook on GitHub
- Trigger webhook and examine request payload
- Create AZ Function with DTO based on request payload
- Test locally using Visual Studio Dev Tunnels
- Deploy to AZ and test remotely
The Azure Function sample code I’ve used above as per step 3 is available on my GitHub Gist.
Please let me know your thoughts or if you have any questions about this post.