
Consuming Webhooks with Logic Apps
Scenario
The scenario covers a situation where you manage a public GitHub repo, but your real development happens in Visual Studio Team Services. In such a case, you want every GitHub issue that gets created to be automatically placed on the VSTS backlog. Let’s have a look!
Logic Apps has an extremely handy webhook trigger. The trigger can be configured, so it registers the webhook when the Logic App gets enabled and that deregistration occurs on a Logic App disable. These are the necessary steps to setup this devops integration scenario.

Register and deregister the webhook
- Create a Logic App with the webhook trigger.
- Configure the trigger properties to register the webhook:
- Subscribe Method: POST
- Subscribe URI: https://api.github.com/repos/{userName}/{repoName}/hooks
- Subscribe Body: request according to the documentation, subscribing solely on the issues event. As the url, you need to pass this expression: @{listCallbackUrl()}
- Configure the trigger properties to un-register the webhook:
- Unsubscribe Method: DELETE
- Unsubscribe URI: compose the URI with the returned GitHub webhook id, via this expression:
@{concat(‘https://api.github.com/repos/{userName}/{repoName}/hooks/’, triggerOutputs().subscribe.body.id)}

- Both operations must use Basic Authentication to authenticate against the GitHub API. The password should be retrieved from Key Vault, at deploy time.

Validate hash-based message authentication code
It’s advised to validate the HMAC code, that is provided in the X-Hub-Signature header, as explained over here. In order to achieve this, we’ll introduce an Azure Function. The basis for this function is taken from this blog.
- Create an Azure Function “ValidateRequest” with this logic:
| #r “System.Security” |
| using System.Net; |
| using System.Text; |
| using System.Security.Cryptography; |
| private const string Sha1Prefix = “sha1=“; |
| public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log) |
| { |
| log.Info(“C# HTTP trigger function processed a request.“); |
| string signatureWithPrefix = req.Headers.GetValues(“X-Hub-Signature“).FirstOrDefault(); |
| log.Info(“Signature: “ + signatureWithPrefix); |
| string sharedSecret = req.Headers.GetValues(“X-LogicApp-Secret“).FirstOrDefault(); |
| log.Info(“Secret: “ + sharedSecret); |
| var payload = await req.Content.ReadAsStringAsync(); |
| log.Info(“Payload: “ + payload); |
| bool isValid = IsGithubPushAllowed(payload, signatureWithPrefix, sharedSecret); |
| return isValid == false |
| ? req.CreateResponse(HttpStatusCode.BadRequest, “Missing or invalid Github security headers!“) |
| : req.CreateResponse(HttpStatusCode.OK); |
| } |
| public static bool IsGithubPushAllowed(string payload, string signatureWithPrefix, string sharedSecret) |
| { |
| if (string.IsNullOrWhiteSpace(payload)) |
| { |
| throw new ArgumentNullException(nameof(payload)); |
| } |
| if (string.IsNullOrWhiteSpace(signatureWithPrefix)) |
| { |
| throw new ArgumentNullException(nameof(signatureWithPrefix)); |
| } |
| if (signatureWithPrefix.StartsWith(Sha1Prefix, StringComparison.OrdinalIgnoreCase)) |
| { |
| var signature = signatureWithPrefix.Substring(Sha1Prefix.Length); |
| var secret = Encoding.ASCII.GetBytes(sharedSecret); |
| var payloadBytes = Encoding.ASCII.GetBytes(payload); |
| using (var hmSha1 = new HMACSHA1(secret)) |
| { |
| var hash = hmSha1.ComputeHash(payloadBytes); |
| var hashString = ToHexString(hash); |
| if (hashString.Equals(signature)) |
| { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| public static string ToHexString(byte[] bytes) |
| { |
| var builder = new StringBuilder(bytes.Length * 2); |
| foreach (byte b in bytes) |
| { |
| builder.AppendFormat(“{0:x2}“, b); |
| } |
| return builder.ToString(); |
| } |
- Call this Azure Function from within the Logic App:
- Pass the complete request body
- Pass the HTTP headers and add the “X-LogicApp-Secret” header via this expression:
@addProperty(triggerOutputs()[‘headers’], ‘X-LogicApp-Secret’, ‘ThisIsMySecret’)

Handle the GitHub webhook
- Use the Parse JSON action to be able to easily access the event data in the next actions. Use a sample request to generate the JSON schema.

- GitHub sends each 5 minutes a ping request. Implement some decision logic to cancel the workflow in case we receive such a ping request. This can be verified through the X-GitHub-Event HTTP header.

- Another decision we need is to only continue processing in the case it’s a new issue that was created. This can be determined through the action, which should be equal to opened.

Create the VSTS work item
- Configure the “Create a work item” action, including the title and description taken from the GitHub event.

Testing
- When we create an issue in GitHub:

- The Logic App gets immediately fired:

- And a VSTS work item is created:

Conclusion
Let’s see whether we fulfilled all responsibilities for webhook consumers:
- Availability: is native to serverless technology
- Scalability: is native to serverless technology, we can add throttling via concurrency control

- Reliability: Logic Apps automatically persists the message. Retry policies can be configured towards the backend system:

- Security: HMAC code has been verified through Azure Function
- Sequencing: not really needed, only new issues are taken into account
Logic Apps provides seamless and incredibly easy integration for webhooks!