Webhooks
  • 17 Oct 2023
  • 8 Minutes to read
  • Dark
    Light
  • PDF

Webhooks

  • Dark
    Light
  • PDF

Article Summary

Webhooks let you connect ProGet with other services to notify users, trigger automated workflows, or audit certain events. For example, you could

  • Send an instant message when a package is added to a feed
  • Deploy a package with BuildMaster once it's promoted to a different feed
  • Log a message in a third-party auditing tool when a package is deleted

This is done by creating webhooks that will send HTTP-based notifications (payloads) when certain events like package publishing, deployment, and deletion occur. These payloads can be adapted to fit into nearly any other tool's API, including workflow automation services such as Zappier, Microsoft Flow, etc.

This feature is available in paid and trial ProGet editions.

Configuring Webhooks

Webhooks are configured via the Administration > Webhooks page, and can be configured for all feeds or individual feeds. Multiple webhooks can be specified, but the order of invocation is not guaranteed.

Usage Note: Webhooks are intended for basic notification and triggering purposes only. Do not attempt to "chain" multiple webhooks to orchestrate a series of sequential events; it may just happen to work in testing, but the order of webhook invocation is not predictable and may occur in parallel.

A webhook may be associated with a feed, and has the following properties:

  • Id – a system-generated unique identifier
  • Name - a non-unique identifier used to distinguish webhooks in the UI and logs
  • Event Type - one of the event types
  • URL - the URL to send the request to
  • Conditional - an expression that is evaluated to see whether the webhook should run
  • Custom payload - customize the request that is sent instead of the default payload
    • Method - one of GET, DELETE, POST, PUT, HEAD, OPTIONS, PATCH
    • Headers - a new-line separate list of HTTP headers
    • Body - the body of the request that will be sent

Event Types

Webhooks can be configured for the following events:

  • added - a package is added to a feed using the API, web interface, or a drop-path
  • promoted - a package is promoted using the API or web interface
  • deployed - a deployment record is added; note that this occurs when certain HTTP headers are submitted during an API-based package download
  • deleted - a package is deleted from a feed using the API or web interface
  • purged - a package was deleted because of a retention policy

Conditional

The conditional is an optional expression that is evaluated prior to a webhook being invoked. This takes the same format as an OtterScript predicate expression, which has a standard unary operator (!),equality operators (== and !=), boolean comparison (&& and ||), and parenthesis.

Following are some example expressions and their results.

ExpressionResult
$FeedType == NuGetIf the feed type is NuGet, the webhook will execute; note that nuget will always evaluate to false; because it's a case-sensitive comparison
trueThe webhook will always execute
!trueThe webhook will never execute
true && falseThe webhook will never execute
$CustomVar == trueIf $CustomVar is a defined variable, and it's "true", then the webhook will execute; otherwise, an error will occur if a variable is not defined

Default Payload

By default, ProGet will POST a simple JSON-based object, with key/values containing package information and event type. Note that these use variables to send package-specific data.

Headers:

  Content-Type=application/json

Content

$ToJson(%(
    feed: $FeedName,
    package: $PackageId,
    version: $PackageVersion,
    hash: $PackageHash,
    packageType: $FeedType,
    event: $WebhookEvent,
    user: $UserName
))

Workflow Automation Services

When a basic HTTP request isn't enough to perform a particular task, you can use third-party workflow automation services to act as a sort of bridge between ProGet and the tools you want to automate.

There are lots of hosted and on-prem workflow automation services on the market – Zapier, IFTT, Microsoft Flow, elastic.io, and so on – and while they all function in a slightly different way, they'll all integrate with ProGet in just about the same way: ProGet will send an HTTP request to the automation service, and the automation service will trigger some workflow that may dispatch additional requests to other APIs.

Example: Zapier

In Zapier, automation workflows are called Zaps. When you create a new Zap with a webhook-enabled trigger, you'll receive a new URL that looks something like this:

https://zapier.com/hooks/catch/n/Lx2RH/

Zappier prides itself on making things "just work" when it comes to webhooks, and since ProGet's default payload is a simple JSON-based key/value pair, you can use that to get started.

After entering the Zap URL on a webhook for a feed, ProGet will start sending data to Zapier when the selected event occurs. Zappier will parse the fields (feed, group, name, version, etc.) automatically.

From there, you can use those values for outbound integrations. See Zapier's Webhook Documentation for more details and troubleshooting.

Authentication to Other Services

While all APIs manage authentication a bit differently, you're usually provided with at least one of two options: using an API key, or basic access authentication. Both of these authentication methods can be done within a Webhook, though you might need to use a custom payload.

API keys are often sent on the query string, message body, or in an HTTP header. You can add these wherever appropriate in a webhook.

Basic access authentication is actually just an HTTP header. You can include it in the headers section of the webhook, as a line like this:

Authorization: Basic $EncodeBasicAuth(username,password)

In both cases, it's a good practice to use variables to store keys, names, and passwords. Although this doesn't secure them (they are always sent as plain text over HTTP/S), it will often obscure them from causal viewing and make it easier to reuse in different webhooks.

Variables

You can use variables to combine static configuration information (such as API keys), context-specific data (like user name), and package metadata (such as version numbers) to create all sorts of custom payloads.

Configuration Variables

Configuration variables are static, key/value pairs that can be defined either globally, or on a feed. If you define a feed-level variable with the same name as a global variable, then the feed-level variable will be used when events occur in the context of that feed.

Variable names must be no more than fifty characters: numbers (0-9), upper- and lower-case letters (a-Z), dashes (-), and underscores (_); must start with a letter, and may not start or end with a dash or underscore.

Variable Functions

You may have seen expressions like $JSEncode($PackageGroup) in the default payload; these are variable functions, which take in a number of parameters and return a value. All of the "built-in variables" like $FeedName, $PackageVersion, and $Date are implemented as variable functions.

You can create configuration variables with the same name as a variable function, but we don't recommend it because it can get quite confusing when things like $FeedName don't return the actual feed name. If there is a configuration variable of the same name, that value will be used instead, unless you explicitly reference the parameter list (such as $FeedName()).

Variable functions are extensible, so you can write your own with an Inedo Extension.

Reading Package Metadata

Variable functions are used to extract metadata from packages. There are several built-in functions that allow you to access common metadata across all packages: $PackageName, $PackageVersion, etc. You can also use the $PackageProperty() function.

For example, when $PackageProperty(_sourceTarget) is used on a universal package, the _sourceTarget property is returned. If there is no such property, then an error will be logged and the webhook execution will not occur.

However, if you specify the second argument (defaultText) to the PackageProperty function, then it will succeed. That is, $PackageProperty(_sourceTarget, unspecified) will return the value of _sourceTarget or unspecified.

Escaping $ and Unresolvable Variables

Anything that "looks" like a variable – i.e. text that starts with a $, then a character – will be parsed as a variable expression, and evaluated. If a configuration variable or variable function cannot be found, then an error will be logged and the request will not complete. This is usually what you'd want, and will help you track down a typo like $PackagName.

However, if you actually want a $-character somewhere in your payload, you'll need to escape it using a grave apostrophe (`) like this: `$. If you want to use a grave apostrophe, then you'll also need to escape it like this: ``. Whitespace character expansion is also available with `r , `n, and `t.

Built-in Variables

VariableResult
$WebhookNamereturns the name of the currently running webhook
$WebhookIdreturns the id of the currently running webhook
$WebhookEventreturns the name of the event
$FeedIdreturns the id of the feed in context
$FeedNamereturns the name of the feed in context
$FeedTypereturns the type of the feed in context; NuGet, Chocolatey, PowerShell, {…}
$Date(format)same description
$PackageGroupreturns the package's group name, or empty
$PackageNamereturns the package's name or empty if it only has an Id
$PackageIdreturns the package's Id
$PackageVersionreturns the package version
$PackageHashreturns the package hash, if it had one computed already
$PackageProperty(name, default)returns an arbitrary property from the metadata; when default is specified, that text is returned in place of an error occurring when a property doesn't exist
$Usernamereturns the name of the user who initiated the event; this will be SYSTEM if it was triggered by the service (a drop path, for example) or Anonymous
$EncodeBasicAuth(username,password)returns the base64 string used in basic auth requests
$JSEncode(s)returns a value that has been encoded for use in a javascript/JSON string
$Coalesce(s, …)returns the first non-whitespace value from the arguments

Error Handling and Debugging

ProGet will not wait for a response from the external server to continue. When an error does occur (either from bad HTTP status codes or network-level issues), it will be saved to ProGet's Diagnostics Center. You can view these errors by navigating to Administration -> Diagnostics Center and then clicking the View Messages button. There is also a Category filter to only show webhook messages.

Note that, some errors -- such as variables being unable to be replaced, or an invalid URL -- will cause a HTTP request to not be made.

Webhook requests may be made from either the web or service, depending on where the event occurred. For example, a package promote request will always come from the web node that actually received the request, whereas a package add request may come from the web node (web UI or API) or the service (drop path).


Was this article helpful?