Agent skill

Azure DevOps Webhooks Skill

Receive and authenticate Azure DevOps webhooks (Web Hooks service hooks). Use when setting up an Azure DevOps service hook endpoint, verifying the basic authentication credentials, debugging 401s, or handling events like git.push, git.pullrequest.created, git.pullrequest.merged, workitem.created, workitem.updated and build.complete.

Install this skill

npx skills add hookdeck/webhook-skills --skill azure-devops-webhooks


The Web Hooks service-hook consumer (webHooks / httpRequest) POSTs a JSON event to your URL when something happens in an Azure DevOps project: a push, a pull request, a work item change, a build or pipeline run. Payloads are not signed. Authentication is HTTP Basic: the username and password you set on the subscription arrive on every request as Authorization: Basic base64(username:password), and your endpoint checks them.

This skill covers the basicAuthCredentials setting (HTTPS is required for it). The alternative httpHeaders setting is not covered.

A static shared credential authenticates the sender, not the body, and gives no replay protection (there is no signature and no timestamp). Use HTTPS, and rotate the credential if it leaks.

When to Use This Skill

  • How do I receive Azure DevOps webhooks / service hooks?
  • How do I verify the basic authentication credentials on an Azure DevOps webhook?
  • Does Azure DevOps sign webhook payloads? (No: use the shared basic auth credentials)
  • How do I handle git.push, git.pullrequest.created, workitem.updated or build.complete?
  • Why does my Azure DevOps webhook endpoint return 401?

Verification (core)

const crypto = require('crypto');

// Hash both sides: equal-length digests, so timingSafeEqual never throws.
const safeEqual = (a, b) => crypto.timingSafeEqual(
  crypto.createHash('sha256').update(a).digest(),
  crypto.createHash('sha256').update(b).digest());

function verifyAzureDevOpsRequest(headers, user = process.env.AZURE_DEVOPS_WEBHOOK_USERNAME,
                                  pass = process.env.AZURE_DEVOPS_WEBHOOK_PASSWORD) {
  if (!user || !pass) return false;                         // never accept when unconfigured
  const m = /^basic +([A-Za-z0-9+/]+={0,2})\s*$/i.exec(headers.authorization || '');
  if (!m) return false;                                     // scheme is case-insensitive
  const decoded = Buffer.from(m[1], 'base64');
  const i = decoded.indexOf(0x3a);                          // split on the FIRST colon
  if (i < 0) return false;
  const userOk = safeEqual(decoded.subarray(0, i), Buffer.from(user));
  const passOk = safeEqual(decoded.subarray(i + 1), Buffer.from(pass));
  return userOk && passOk;                                  // evaluate both, no short-circuit
}
  • Header: Authorization: Basic base64(username:password). Env vars: AZURE_DEVOPS_WEBHOOK_USERNAME and AZURE_DEVOPS_WEBHOOK_PASSWORD.
  • Python: base64.b64decode(..., validate=True), partition(b":"), then hmac.compare_digest on bytes (encode both sides; a str with non-ASCII characters raises TypeError).
  • Return 401 for a missing or malformed header or any mismatch, 400 for a body that isn't a single JSON object, 200 once handled.
  • Read the body raw (express.raw({ type: '*/*' })) so any content type works, and parse JSON only after the credential check.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Event Types

Every payload has eventType (also called the event ID), publisherId, message, detailedMessage, resource, resourceVersion, resourceContainers and createdDate, plus id, and usually subscriptionId and notificationId. The handlers in the examples cover:

eventTypeSent When
git.pushCode is pushed to a Git repository
git.pullrequest.createdA pull request is created
git.pullrequest.updatedA pull request is updated
git.pullrequest.mergedA pull request is merged
workitem.createdA work item is created
workitem.updatedA work item changes
workitem.commentedA work item is commented on
build.completeA build completes
ms.vss-pipelines.run-state-changed-eventA pipeline run changes state

Many more exist (releases, approvals, repositories, service connections, advanced security); see references/overview.md.

The docs' build.complete and ms.azure-devops-release.deployment-approval-pending-event samples are malformed JSON (two objects concatenated with no separator). Don't copy them into fixtures or tests verbatim; a real delivery is a single object.

Environment Variables

AZURE_DEVOPS_WEBHOOK_USERNAME=your-webhook-username   # Service hook > Action > Basic authentication username
AZURE_DEVOPS_WEBHOOK_PASSWORD=your-webhook-password   # Service hook > Action > Basic authentication password

Local Development

Azure DevOps can't reach localhost (webhooks can't target loopback addresses). Tunnel with the Hookdeck CLI (no account required):

npx hookdeck-cli listen 3000 azure-devops --path /webhooks/azure-devops

Use the printed HTTPS URL as the subscription URL.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 3, 2026

View on GitHub →