Skip to content

Commit a1c2f3f

Browse files
authored
Merge pull request #8051 from JKirsch1/refresh-article-on-creating-a-service-hook-sub-programmatically
Freshness Edit: Azure DevOps - Service hooks
2 parents de99ec8 + 443f1eb commit a1c2f3f

File tree

1 file changed

+116
-119
lines changed

1 file changed

+116
-119
lines changed
Lines changed: 116 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,56 @@
11
---
2-
title: Create a service hook subscription programmatically
3-
description: Use service hooks to set up actions to take when specific events occur in Azure DevOps.
2+
title: Create a Service Hook Subscription Programmatically
3+
description: Find out how to programmatically create a service hook subscription that prescribes an action to take when a specified event occurs in Azure DevOps.
44
ms.assetid: 0614F217-4F4E-45DC-A50C-B9FF81F8A5BD
55
ms.custom: engagement-fy23
66
ms.subservice: azure-devops-service-hooks
7-
ms.topic: conceptual
7+
ms.topic: how-to
88
ms.author: chcomley
99
author: chcomley
1010
monikerRange: '<= azure-devops'
11-
ms.date: 10/14/2022
11+
ms.date: 06/25/2025
12+
# customer intent: As a developer, I want to create a service hook subscription programmatically so that I can automate tasks in other services when events happen in my Azure DevOps project.
1213
---
1314

1415
# Create a service hook subscription programmatically
1516

16-
[!INCLUDE [version-lt-eq-azure-devops](../includes/version-lt-eq-azure-devops.md)]
17+
[!INCLUDE [Azure DevOps Services | Azure DevOps Server 2022 | Azure DevOps Server 2020](../includes/version-gt-eq-2020.md)]
1718

18-
Using the [Subscriptions REST APIs](/rest/api/azure/devops/hooks/) , you can programmatically create a subscription that performs an action on an external/consumer service when a specific event occurs in an Azure DevOps project. For example, you can create a subscription to notify your service when a build fails.
19+
You can use a subscription to perform an action on an external or consumer service when a specific event occurs in an Azure DevOps project. For example, a subscription can notify your service when a build fails.
1920

20-
Supported events:
21-
22-
- Build completed
23-
- Code pushed (for Git projects)
24-
- Pull request create or updated (for Git projects)
25-
- Code checked in (TFVC projects)
26-
- Work item created, updated, deleted, restored or commented on
27-
28-
You can configure filters on your subscriptions to control which events trigger an action. For example, you can filter the build completed event based on the build status. For a complete set of supported events and filter options, see the [Event reference](./events.md).
29-
30-
For a complete set of supported consumer services and actions, see the [Consumer reference](./consumers.md).
21+
To create a subscription programmatically, you can use the [Subscriptions REST APIs](/rest/api/azure/devops/hooks/). This article provides a sample request and sample code for creating a subscription.
3122

3223
## Prerequisites
3324

3425
| Category | Requirements |
3526
|--------------|-------------|
3627
|**Project access**| [Project member](../organizations/security/add-users-team-project.md). |
37-
|**Data**|- Project ID. Use the [Project REST API](/rest/api/azure/devops/core/projects) to get the project ID.<br>- Event ID and settings. See the [Event reference](./events.md).<br>- Consumer and action IDs and settings. See the [Consumer reference](./consumers.md).|
28+
|**Data**|- Project ID. Use the [Project REST API](/rest/api/azure/devops/core/projects) to get the project ID.<br>- Event ID and settings. See [Service hook events](events.md).<br>- Consumer and action IDs and settings. See [Service hook consumers](consumers.md).|
29+
30+
## Supported events
31+
32+
Azure DevOps provides support for numerous trigger events. Examples include the following events:
33+
34+
- Build completed
35+
- Code pushed (for Git projects)
36+
- Pull request created or updated (for Git projects)
37+
- Code checked in (for Team Foundation Version Control projects)
38+
- Work item created, updated, deleted, restored, or commented on
39+
40+
To control which events trigger an action, you can configure filters on your subscriptions. For example, you can filter the build completed event based on the build status.
41+
42+
- For a complete set of supported events and filter options, see [Service hook events](events.md).
43+
- For a complete set of supported consumer services and actions, see [Service hook consumers](consumers.md).
3844

39-
## Create the request
45+
## Create a request
4046

41-
Construct the body of the HTTP POST request to create the subscription based on the project ID, event, consumer, and action.
47+
When you create a subscription, you use the body of an HTTP POST request to specify the project ID, event, consumer, action, and related settings.
4248

43-
See the following example request for creating a subscription that causes a build event to POST to `https://myservice/event` when the build `WebSite.CI` fails.
49+
You can use the following request to create a subscription for a build completed event. In this example, when the `WebSite.CI` build fails, the subscription sends a POST request to `https://myservice/event`.
4450

4551
**Request**
46-
```js
52+
53+
```json
4754
{
4855
"publisherId": "tfs",
4956
"eventType": "build.complete",
@@ -53,7 +60,7 @@ See the following example request for creating a subscription that causes a buil
5360
"publisherInputs": {
5461
"buildStatus": "failed",
5562
"definitionName": "WebSite.CI",
56-
"projectId": "56479caf-1eeb-4bca-86ab-aaa6f29399d9",
63+
"projectId": "11bb11bb-cc22-dd33-ee44-55ff55ff55ff",
5764
},
5865
"consumerInputs": {
5966
"url": " https://myservice/event"
@@ -64,9 +71,10 @@ See the following example request for creating a subscription that causes a buil
6471
We highly recommend using secure HTTPS URLs for the security of the private data in the JSON object.
6572

6673
**Response**
67-
See the following response to the request to create the subscription:
6874

69-
```js
75+
The request to create the subscription generates a response that's similar to the following one:
76+
77+
```json
7078
{
7179
"id": "aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e",
7280
"url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/hooks/subscriptions/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e",
@@ -76,19 +84,19 @@ See the following response to the request to create the subscription:
7684
"consumerId": "webHooks",
7785
"consumerActionId": "httpRequest",
7886
"createdBy": {
79-
"id": "00ca946b-2fe9-4f2a-ae2f-40d5c48001bc"
87+
"id": "22cc22cc-dd33-ee44-ff55-66aa66aa66aa"
8088
},
8189
"createdDate": "2014-03-28T16:10:06.523Z",
8290
"modifiedBy": {
83-
"id": "1c4978ae-7cc9-4efa-8649-5547304a8438"
91+
"id": "22cc22cc-dd33-ee44-ff55-66aa66aa66aa"
8492
},
8593
"modifiedDate": "2014-04-25T18:15:26.053Z",
8694
"publisherInputs": {
8795
"buildStatus": "failed",
8896
"definitionName": "WebSite.CI",
89-
"hostId": "17f27955-99bb-4861-9550-f2c669d64fc9",
90-
"projectId": "56479caf-1eeb-4bca-86ab-aaa6f29399d9",
91-
"tfsSubscriptionId": "29cde8b4-f37e-4ef9-a6d4-d57d526d82cc"
97+
"hostId": "d3d3d3d3-eeee-ffff-aaaa-b4b4b4b4b4b4",
98+
"projectId": "11bb11bb-cc22-dd33-ee44-55ff55ff55ff",
99+
"tfsSubscriptionId": "ffff5f5f-aa6a-bb7b-cc8c-dddddd9d9d9d"
92100
},
93101
"consumerInputs": {
94102
"url": "http://myservice/event"
@@ -101,117 +109,106 @@ If the subscription request fails, you get an HTTP response code of 400 with a m
101109

102110
### What happens when the event occurs?
103111

104-
When an event occurs, all enabled subscriptions in the project are evaluated, and the consumer action is performed for all matching subscriptions.
112+
When an event occurs, all enabled subscriptions in the project are evaluated. Then the consumer action is performed for all matching subscriptions.
105113

106114
### Resource versions (advanced)
107115

108116
Resource versioning is applicable when an API is in preview. For most scenarios, specifying `1.0` as the resource version is the safest route.
109117

110-
The event payload sent to certain consumers, like Webhooks, Azure Service Bus, and Azure Storage, includes a JSON representation of subject resource (for example, a build or work item). The representation of this resource can have different forms or versions.
118+
The event payload sent to certain consumers includes a JSON representation of a subject resource. For instance, the payload sent to webhooks, Azure Service Bus, and Azure Storage includes information about a build or work item. The representation of this resource can have various forms or versions.
119+
120+
You can specify the version of the resource that you want to send to the consumer service via the `resourceVersion` field on the subscription.
111121

112-
You can specify the version of the resource that you want to have sent to the consumer service via the `resourceVersion` field on the subscription.
113-
The resource version is the same as the [API version](../integrate/concepts/rest-api-versioning.md). Not specifying a resource version means "latest released". You should always specify a resource version, which ensures a consistent event payload over time.
122+
The resource version is the same as the [API version](../integrate/concepts/rest-api-versioning.md). If you don't specify a resource version, the latest version, `latest released`, is used. To help ensure a consistent event payload over time, always specify a resource version.
114123

115124
## FAQs
125+
116126
### Q: Are there services that I can subscribe to manually?
117127

118-
A: Yes. For more information about the services that you can subscribe to from the administration page for a project, see the [Overview](./overview.md).
128+
A: Yes. For more information about the services that you can subscribe to from a project administration page, see [Integrate with service hooks](overview.md).
119129

120130
### Q: Are there C# libraries that I can use to create subscriptions?
121131

122-
A: No, but here's a sample to help you get started.
132+
A: No, but here's a sample to help you get started. For authentication to Azure DevOps, the following code uses a personal access token (PAT) that's stored in Azure Key Vault. In a production environment, use a more secure authentication method. For more information, see [Choose the right authentication mechanism](../integrate/get-started/authentication/authentication-guidance.md).
123133

124-
```cs
125-
using System;
126-
using System.Collections.Generic;
127-
using System.Linq;
128-
using System.Net;
129-
using System.Net.Http;
130-
using System.Web.Mvc;
134+
```csharp
135+
using Azure.Identity;
136+
using Azure.Security.KeyVault.Secrets;
137+
using Microsoft.VisualStudio.Services.Common;
138+
using Microsoft.VisualStudio.Services.ServiceHooks.WebApi;
139+
using Microsoft.VisualStudio.Services.WebApi;
131140

132-
namespace Microsoft.Samples.VisualStudioOnline
141+
namespace CreateServiceHookSubscription
133142
{
134-
public class ServiceHookEventController : Controller
143+
internal class Program
135144
{
145+
// Create a service hook subscription to send a message to an Azure Service Bus queue when code is pushed to a Git repository.
136146
137-
// POST: /ServiceHookEvent/workitemcreated
138-
[HttpPost]
139-
public HttpResponseMessage WorkItemCreated(Content workItemEvent)
140-
{
141-
//Grabbing the title for the new workitem
142-
var value = RetrieveFieldValue("System.field", workItemEvent.Resource.Fields);
143-
144-
//Acknowledge event receipt
145-
return new HttpResponseMessage(HttpStatusCode.OK);
146-
}
147-
148-
/// <summary>
149-
/// Gets the value for a specified work item field.
150-
/// </summary>
151-
/// <param name="key">Key used to retrieve matching value</param>
152-
/// <param name="fields">List of fields for a work item</param>
153-
/// <returns></returns>
154-
public String RetrieveFieldValue(String key, IList<FieldInfo> fields)
147+
static async Task Main(string[] args)
155148
{
156-
if (String.IsNullOrEmpty(key))
157-
return String.Empty;
158-
159-
var result = fields.Single(s => s.Field.RefName == key);
160-
161-
return result.Value;
149+
// Get the secrets from the key vault.
150+
string keyVaultURI = "https://<key-vault-name>.vault.azure.net/";
151+
var secretClient = new SecretClient(new Uri(keyVaultURI), new DefaultAzureCredential());
152+
string personalAccessTokenSecretName = "<personal-access-token-secret-name>";
153+
string serviceBusConnectionStringSecretName = "<Service-Bus-connection-string-secret-name>";
154+
KeyVaultSecret personalAccessTokenSecret = await secretClient.GetSecretAsync(personalAccessTokenSecretName);
155+
KeyVaultSecret serviceBusConnectionStringSecret = await secretClient.GetSecretAsync(serviceBusConnectionStringSecretName);
156+
157+
// Set up the connection parameters for Azure DevOps.
158+
var azureDevOpsOrganizationURL = new Uri("https://dev.azure.com/<Azure-DevOps-organization-name>/");
159+
string azureDevOpsTeamProjectID = "<Azure-DevOps-team-project-ID>";
160+
string azureDevOpsPersonalAccessToken = personalAccessTokenSecret.Value;
161+
162+
// Set up the event parameters.
163+
string eventPublisherID = "tfs";
164+
string eventID = "git.push";
165+
string eventDescription = "Any stage in any release";
166+
string resourceVersion = "1.0";
167+
168+
// Set up the consumer parameters.
169+
string consumerID = "azureServiceBus";
170+
string consumerActionID = "serviceBusQueueSend";
171+
string serviceBusNamespace = "<Service-Bus-namespace>";
172+
string serviceBusQueueName = "<Service-Bus-queue-name>";
173+
string consumerActionDescription = $"Send a message to the Service Bus {serviceBusQueueName} queue in the {serviceBusNamespace} namespace.";
174+
string serviceBusConnectionString = serviceBusConnectionStringSecret.Value;
175+
176+
// Configure the subscription.
177+
var subscription = new Subscription()
178+
{
179+
PublisherId = eventPublisherID,
180+
PublisherInputs = new Dictionary<string, string>
181+
{
182+
["projectId"] = azureDevOpsTeamProjectID
183+
},
184+
EventType = eventID,
185+
EventDescription = eventDescription,
186+
ResourceVersion = resourceVersion,
187+
ActionDescription = consumerActionDescription,
188+
ConsumerActionId = consumerActionID,
189+
ConsumerId = consumerID,
190+
ConsumerInputs = new Dictionary<string, string>
191+
{
192+
["connectionString"] = serviceBusConnectionString,
193+
["queueName"] = serviceBusQueueName
194+
}
195+
};
196+
197+
// Connect to the Azure DevOps organization and get a service hook client.
198+
var azureDevOpsCredentials = new VssBasicCredential(azureDevOpsPersonalAccessToken, string.Empty);
199+
var azureDevOpsConnection = new VssConnection(azureDevOpsOrganizationURL, azureDevOpsCredentials);
200+
var serviceHookClient = azureDevOpsConnection.GetClient<ServiceHooksPublisherHttpClient>();
201+
202+
// Create the subscription.
203+
var createdSubscription = await serviceHookClient.CreateSubscriptionAsync(subscription);
204+
Console.WriteLine($"A subscription was created that has ID {createdSubscription.Id}.");
162205
}
163-
164-
}
165-
166-
public class Content
167-
{
168-
public String SubscriptionId { get; set; }
169-
170-
public int NotificationId { get; set; }
171-
172-
public String EventType { get; set; }
173-
174-
public WorkItemResource Resource { get; set; }
175-
176-
}
177-
178-
public class WorkItemResource
179-
{
180-
public String UpdatesUrl { get; set; }
181-
182-
public IList<FieldInfo> Fields { get; set;}
183-
184-
public int Id { get; set; }
185-
186-
public int Rev { get; set; }
187-
188-
public String Url { get; set; }
189-
190-
public String WebUrl { get; set; }
191-
}
192-
193-
public class FieldInfo
194-
{
195-
public FieldDetailedInfo Field { get; set; }
196-
197-
public String Value { get; set; }
198-
199-
}
200-
201-
public class FieldDetailedInfo
202-
{
203-
public int Id { get; set; }
204-
205-
public String Name { get; set; }
206-
207-
public String RefName { get; set; }
208206
}
209207
}
210208
```
211209

212-
## Related articles
210+
## Related content
213211

214-
- [Authorize service hooks](authorize.md)
215-
- [Consumers](consumers.md)
216-
- [Events](events.md)
217-
- [Troubleshooting and FAQs](troubleshoot.md)
212+
- [Manage authorization of services to access Azure DevOps](authorize.md)
213+
- [Service hooks events](events.md)
214+
- [Troubleshoot service hooks](troubleshoot.md)

0 commit comments

Comments
 (0)