---
title: Webhooks
url: /docs/esc/environments/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.

## Overview

ESC Webhooks can be attached to either an environment or a Pulumi Cloud organization:

- **Environment webhooks** will fire in response to events you specify for a single ESC environment.
- **Organization webhooks** can be configured to trigger in response to events you specify for _all_ environments in your Pulumi Cloud organization, in addition to other organization-wide events.

> **Note:** Pulumi Cloud also supports webhooks for events related to Pulumi IaC stacks and [Pulumi Deployments](/docs/pulumi-cloud/deployments). For additional information on these types of webhooks, see [Pulumi Cloud Webhooks](/docs/deployments/webhooks).

> **Note:** Webhooks do not guarantee ordered delivery of events. That is, you should not assume events will be received in the order in which they occurred.

### Create a Webhook

Pulumi Webhooks may be created through any of the following methods:

1. Manually, in the Pulumi Cloud UI using the steps outlined in [Create an Organization Webhook in the Pulumi Cloud UI](#create-an-organization-webhook-in-the-pulumi-cloud-ui) or [Create an Environment Webhook in Pulumi Cloud in the Pulumi Cloud UI](#create-an-environment-webhook-in-the-pulumi-cloud-ui).
1. Declaratively, as part of a [Pulumi IaC](/docs/iac) program as shown in [Create a Webhook in a Pulumi IaC Program](#create-a-webhook-in-a-pulumi-iac-program)
1. By invoking the [Pulumi Cloud REST API](/docs/reference/cloud-rest-api/webhooks/#create-webhook) directly.

#### Create an Environment Webhook in the Pulumi Cloud UI

1. Navigate to the environment you wish to create a webhook for.
1. Navigate to **Webhooks** tab.
1. Select **Create webhook**.
1. Under Destination, choose **Webhook**, **Slack**, **Microsoft Teams** or **Deployment**
   1. For generic JSON webhooks, provide a display name, payload URL, and optionally a secret.
   1. For Slack webhooks, provide a Slack webhook URL and a display name.
   1. For Microsoft Teams webhooks, provide a Microsoft Teams webhook URL and a display name.
   1. For Deployment webhooks, provide the stack to deploy in the format `project/stack`.
1. Choose which events you would like to receive using groups and filters menu.

#### Create an Organization Webhook in the Pulumi Cloud UI

> **Note:** You must be an admin of the organization to create organization webhooks.

1. Select **Settings** > **Integrations** > **Webhooks**.
1. Select **Create webhook**.
1. Under Destination, choose **Webhook**, **Slack** or **Microsoft Teams**.
1. For generic JSON webhooks, provide a display name, payload URL, and optionally a secret.
1. For Slack webhooks, provide a Slack webhook URL and a display name.
1. For Microsoft Teams webhooks, provide a Microsoft Teams webhook URL and a display name.
1. Choose which events you would like to receive using groups and filters menu.

#### Create a 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](/registry/packages/pulumiservice/api-docs/webhook/) with the [Pulumi Cloud provider](/registry/packages/pulumiservice).

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.

<!-- chooser: language -->

<!-- option: typescript -->
```typescript
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",
});

```

<!-- /option -->

<!-- option: python -->
```python
import pulumi
import pulumi_pulumiservice as pcloud
webhook = pcloud.Webhook("example-webhook",
    active=True,
    display_name="webhook example",
    organization_name="example",
    environment_name="my-environment",
    payload_url="https://example.com/webhook"
)

```

<!-- /option -->

<!-- option: go -->
```go
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
 })
}

```

<!-- /option -->

<!-- option: csharp -->
```csharp
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"
        })
    }
}

```

<!-- /option -->

<!-- /chooser -->

## 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](https://api.slack.com/messaging/webhooks)
and optionally choosing which events you want delivered using [event groups and filters](#event-filtering).

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.

[Create a Slack app from manifest](https://api.slack.com/apps?new_app=1&manifest_yaml=display_information%3A%0A%20%20name%3A%20pulumi-slack-notifications%0A%20%20description%3A%20Funnel%20Pulumi%20webhooks%20to%20Slack%0A%20%20background_color%3A%20%22%238a3391%22%0Afeatures%3A%0A%20%20bot_user%3A%0A%20%20%20%20display_name%3A%20pulumi-slack-notifications%0A%20%20%20%20always_online%3A%20false%0Aoauth_config%3A%0A%20%20scopes%3A%0A%20%20%20%20bot%3A%0A%20%20%20%20%20%20-%20incoming-webhook%0Asettings%3A%0A%20%20org_deploy_enabled%3A%20false%0A%20%20socket_mode_enabled%3A%20false%0A%20%20token_rotation_enabled%3A%20false)

### 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](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook)
and optionally choosing which events you want delivered using [event groups and filters](#event-filtering).

### Deployment Webhooks

The Deployment webhook destination lets you trigger updates on your stacks via [Pulumi Deployments](/docs/deployments/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](/docs/deployments/deployments/reference/#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.

> **Note:** If a secret is provided, webhook deliveries will contain a signature in the HTTP request header that can be used
to authenticate messages as coming from the Pulumi Cloud.

#### 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

```json
{
  "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

```json
{
  "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

```json
{
  "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

```json
{
  "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

```json
{
  "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](https://github.com/danharper/hmac-examples).

<!-- chooser: language -->

<!-- option: javascript -->
```javascript
var crypto = require('crypto');

const sharedSecret = ...
const payload = req.body.toString();

var hmacAlg = crypto.createHmac('sha256', sharedSecret);
var expectedSignature = hmac.update(payloadBody).digest('hex');

```

<!-- /option -->

<!-- option: typescript -->
```typescript
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));

```

<!-- /option -->

<!-- option: python -->
```python
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())

```

<!-- /option -->

<!-- option: go -->
```go
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))
}

```

<!-- /option -->

<!-- /chooser -->

## Webhook deliveries

Pulumi Cloud records the recent deliveries for each webhook so you can confirm that events
were received and troubleshoot any that failed. Environment and organization webhook
deliveries can be viewed and redelivered from the webhook's page in the Pulumi Cloud UI.
For step-by-step instructions, see [Webhook deliveries](/docs/deployments/webhooks/#webhook-deliveries)
on the Pulumi Cloud Webhooks page.

The experimental `pulumi stack webhook delivery` CLI commands apply to stack webhooks
only, so ESC environment and organization webhooks must be redelivered from the UI.

## Additional Resources

- [Managing Github Webhooks with Pulumi](/blog/managing-github-webhooks-with-pulumi/)
- [Pulumi Cloud REST API](/docs/pulumi-cloud/cloud-rest-api/)

