ESC Webhooks
ESC Webhooks allow you to notify external services of events
happening within your ESC environments. For example,
you can trigger a notification whenever a new revision of an environment is created.
When an event occurs, Pulumi will notify the registered webhook listeners via a HTTP POST
request with metadata about the event. The webhook can then be used to emit a
notification, start running integration tests, or even update Pulumi stacks.
There are large number of real life applications for webhooks including serving as the foundation of most ChatOps workflows.
Overview
ESC Webhooks can be attached to either an environment or an organization. Environment webhooks will be notified of events specific to the environment. Organization webhooks will be notified for events happening within each of the organization’s environments.
Organization webhooks can be managed on the Organization Settings page. Environment webhooks can be managed from the webhooks tab on each Environment’s detail page.
Pulumi Cloud also supports webhooks for events related to Pulumi IaC stacks and Pulumi Deployments. For additional information on these types of webhooks, see Pulumi Cloud Webhooks.
Create a Webhook
Pulumi Webhooks may be created through any of the following methods:
- Manually, in the Pulumi Cloud UI using the steps outlined in Create an Organization Webhook in the Pulumi Cloud UI or Create an Environment Webhook in Pulumi Cloud in the Pulumi Cloud UI.
- Declaratively, as part of a Pulumi IaC program as shown in Create an Webhook in a Pulumi IaC Program
- By invoking the Pulumi Cloud REST API directly.
Create an Organization Webhook in the Pulumi Cloud UI
- Navigate to Settings > Webhooks.
- Select Create webhook.
- Under Destination, choose Webhook, Slack or Microsoft Teams.
- For generic JSON webhooks, provide a display name, payload URL, and optionally a secret.
- For Slack webhooks, provide a Slack webhook URL and a display name.
- For Microsoft Teams webhooks, provide a Microsoft Teams webhook URL and a display name.
- Choose which events you would like to receive using groups and filters menu.
Create an Environment Webhook in the Pulumi Cloud UI
- Navigate to your environment.
- Navigate to Webhooks tab.
- Select Create webhook.
- Under Destination, choose Webhook, Slack, Microsoft Teams or Deployment
- For generic JSON webhooks, provide a display name, payload URL, and optionally a secret.
- For Slack webhooks, provide a Slack webhook URL and a display name.
- For Microsoft Teams webhooks, provide a Microsoft Teams webhook URL and a display name.
- For Deployment webhooks, provide the stack to deploy in the format
project/stack
.
- Choose which events you would like to receive using groups and filters menu.
Create an Webhook in a Pulumi IaC Program
The following example shows how to create an Environment webhook in a Pulumi IaC program by declaring a Webhook resource with the Pulumi Cloud provider.
To create an Organization webhook instead of an Environment webhook, the code is virtually identical - just omit the environmentName
value when declaring the webhook resource.
import * as pulumi from "@pulumi/pulumi";
import * as pulumiservice from "@pulumi/pulumiservice";
const webhook = new pulumiservice.Webhook("example-webhook", {
active: true,
displayName: "webhook example",
organizationName: "example",
environmentName: "my-environment",
payloadUrl: "https://example.com/webhook",
});
import pulumi
import pulumi_pulumiservice
webhook = pulumi_service.Webhook("example-webhook",
active: True,
display_name: "webhook example",
organization_name: "example",
environmentName: "my-environment",
payload_url: "https://example.com/webhook",
)
import (
"fmt"
"github.com/pulumi/pulumi-pulumiservice/sdk/go/pulumiservice"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
webhook, err := pulumiservice.NewWebhook(ctx, "example-webhook", &pulumiservice.WebhookArgs{
Active: pulumi.Bool(true),
DisplayName: pulumi.String("example webhook"),
OrganizationName: pulumi.String("example"),
EnvironmentName: pulumi.String("my-environment"),
PayloadURL: pulumi.String("https://example.com/webhook"),
}, nil)
if err != nil {
return fmt.Errorf("error creating webhook: %v", err)
}
return nil
})
}
using Pulumi;
using Pulumi.PulumiService;
class PulumiServiceWebhook: Stack
{
public PulumiServiceWebhook()
{
var webhook = new Webhook("example-webhook", new WebhookArgs{
Active = true,
DisplayName = "example webhook",
OrganizationName = "example",
EnvironmentName = "my-environment",
PayloadUrl = "https://example.com/webhook"
})
}
}
Event Filtering
Event filtering allows you to choose which events should be delivered to each webhook. You may choose to receive all events in a group, or filter to specific events (only failures, only deployment events, etc.). The following table describes the various event filters available and the context in which they are relevant.
Filter | Event Kind | Webhook Type | Triggered |
---|---|---|---|
environment_created | environment | Organization webhooks only | When a new environment is created. |
environment_deleted | environment | Organization webhooks only | When an environment is deleted. |
environment_revision_created | environment_revision | Organization or Environment | When a new revision is created on an environment. |
environment_revision_retracted | environment_revision | Organization or Environment | When a revision is retracted on an environment. |
environment_revision_tag_created | environment_revision_tag | Organization or Environment | When a new revision tag is created. |
environment_revision_tag_deleted | environment_revision_tag | Organization or Environment | When a revision tag is deleted. |
environment_revision_tag_updated | environment_revision_tag | Organization or Environment | When a revision tag is updated. |
environment_tag_created | environment_tag | Organization or Environment | When a new environment tag is created. |
environment_tag_deleted | environment_tag | Organization or Environment | When an environment tag is deleted. |
environment_tag_updated | environment_tag | Organization or Environment | When an environment tag is updated. |
imported_environment_changed | imported_environment_changed | Organization or Environment | When an imported environment was changed. |
There is also an environments
filter group that lets you easily subscribe to all environment events.
Group | Event Kinds Included |
---|---|
environments | environment , environment_revision , environment_revision_tag , environment_tag , imported_environment_changed |
Webhook Formats
When creating a webhook, you can choose between a generic JSON webhook payload, slack
formatted events and ms_teams
formatted events.
Slack Webhooks
Slack Webhooks allow you to seamlessly integrate notifications about your Pulumi resources into your Slack workspace by simply providing a Slack incoming webhook URL and optionally choosing which events you want delivered using event groups and filters.
You can either create your own Slack app (or use an existing one you may already have installed in your workspace), or follow the link below to quickly get started with a pre-defined Slack app manifest.
Microsoft Teams Webhooks
Microsoft Teams Webhooks allow you to seamlessly integrate notifications about your Pulumi stacks and organizations into your Microsoft Teams workspace by simply providing a Microsoft Teams incoming webhook URL and optionally choosing which events you want delivered using event groups and filters.
Deployment Webhooks
The Deployment webhook destination lets you trigger updates on your stacks via Pulumi Deployments, usually in response to environment_revision_created
or imported_environment_changed
events. This enables you to keep dependent stacks up to date with your environment changes automatically.
Deployment webhooks require that your stacks are configured with Deployment Settings.
Generic JSON Webhooks
When using generic JSON webhooks, Pulumi will send an HTTP POST
request to
all registered webhooks. The webhook can then be used to emit a
notification, start running integration tests, or even update additional stacks.
Payload Examples
Each webhook payload has a format specific to the payload being emitted. Every payload will contain a sender, organization, and stack reference as appropriate. For examples of specific payloads, see Payload Reference below.
Each webhook will contain a user
field, which is the user who requested the action, an organization
which is
the organization name, and a URL for the event. It will also contain projectName
and environmentName
when applicable.
Environment
{
"user": {
"name": "Morty Smith",
"githubLogin": "morty",
"avatarUrl": "https://crazy-adventures.net/morty.png"
},
"organization": {
"name": "Crazy Adventures",
"githubLogin": "crazy-adventures",
"avatarUrl": "https://crazy-adventures.net/logo.png"
},
"projectName": "website",
"environmentName": "prod",
"action": "created",
}
Environment Revision
{
"user": {
"name": "Morty Smith",
"githubLogin": "morty",
"avatarUrl": "https://crazy-adventures.net/morty.png"
},
"organization": {
"name": "Crazy Adventures",
"githubLogin": "crazy-adventures",
"avatarUrl": "https://crazy-adventures.net/logo.png"
},
"projectName": "website",
"environmentName": "prod",
"action": "created",
"revision": 5
}
Environment Revision Tag
{
"user": {
"name": "Morty Smith",
"githubLogin": "morty",
"avatarUrl": "https://crazy-adventures.net/morty.png"
},
"organization": {
"name": "Crazy Adventures",
"githubLogin": "crazy-adventures",
"avatarUrl": "https://crazy-adventures.net/logo.png"
},
"projectName": "website",
"environmentName": "prod",
"tagName": "stable",
"action": "created",
"revision": 5
}
Environment Tag
{
"user": {
"name": "Morty Smith",
"githubLogin": "morty",
"avatarUrl": "https://crazy-adventures.net/morty.png"
},
"organization": {
"name": "Crazy Adventures",
"githubLogin": "crazy-adventures",
"avatarUrl": "https://crazy-adventures.net/logo.png"
},
"projectName": "website",
"environmentName": "prod",
"tagName": "stable",
"action": "created"
}
Imported Environment Changed
{
"user": {
"name": "Morty Smith",
"githubLogin": "morty",
"avatarUrl": "https://crazy-adventures.net/morty.png"
},
"organization": {
"name": "Crazy Adventures",
"githubLogin": "crazy-adventures",
"avatarUrl": "https://crazy-adventures.net/logo.png"
},
"projectName": "website",
"environmentName": "prod",
"affectedRevisions": [2, 5, 6, 7],
"importedEnvironmentReference": {
"projectName": "website",
"environmentName": "base",
"revision": 10
}
}
Headers
Payloads contain several headers.
Header | Description |
---|---|
Pulumi-Webhook-ID | Unique ID for each webhook sent which you can reference when looking at delivery logs in the Pulumi Cloud. |
Pulumi-Webhook-Kind | The kind of webhook event, e.g. environment . |
Pulumi-Webhook-Signature | Only set if the webhook has a shared secret. HMAC hex digest of the request payload, using the sha256 hash function and the webhook secret as the HMAC key. |
The following snippets show how to compute and verify the webhook signature. For examples in other languages, see danharper/hmac-examples.
var crypto = require('crypto');
const sharedSecret = ...
const payload = req.body.toString();
var hmacAlg = crypto.createHmac('sha256', sharedSecret);
var expectedSignature = hmac.update(payloadBody).digest('hex');
import * as crypto from "crypto";
const sharedSecret = ...
const payload = req.body.toString();
const hmacAlg = crypto.createHmac("sha256", stackConfig.sharedSecret);
const hmac = hmacAlg.update(payload).digest("hex");
const result = crypto.timingSafeEqual(Buffer.from(webhookSig), Buffer.from(hmac));
import hashlib
import hmac
import base64
message = bytes('...', 'utf-8')
secret = bytes('...', 'utf-8')
hash = hmac.new(secret, message, hashlib.sha256)
hash.hexdigest()
expected_signature = base64.b64encode(hash.digest())
func computeSignature(payload []byte, secret string) string {
mac := hmac.New(sha256.New, []byte(secret))
_, err := mac.Write(payload)
contract.AssertNoErrorf(err, "computing HMAC digest")
return fmt.Sprintf("%x", mac.Sum(nil))
}
Additional Resources
Thank you for your feedback!
If you have a question about how to use Pulumi, reach out in Community Slack.
Open an issue on GitHub to report a problem or suggest an improvement.