# Azure Event Grid triggers

> How do I trigger scripts and flows from Azure Event Grid events in Windmill?

Windmill can connect to [**Azure Event Grid**](https://learn.microsoft.com/en-us/azure/event-grid/overview) and trigger runnables (scripts, flows) when events are delivered from custom topics, system topics, domains, or Event Grid Namespace topics.

Azure Event Grid triggers are a [self-hosted Enterprise](/pricing) feature. They are disabled on the [Cloud](/pricing).

## Trigger modes

A single Azure trigger covers three delivery modes, selected via the **Edition** (Basic / Namespace) and **Delivery** (Push / Pull) toggles in the editor:

| Mode | Edition | Delivery | Use when |
|------|---------|----------|----------|
| `basic_push` | Basic | Push | Reacting to first-party Azure events from custom topics, [system topics](https://learn.microsoft.com/en-us/azure/event-grid/system-topics) (Storage, Resource Manager, Key Vault, Service Bus, IoT Hub control-plane, etc.) or [domains](https://learn.microsoft.com/en-us/azure/event-grid/event-domains). |
| `namespace_push` | Namespace | Push | Pushing [CloudEvents 1.0](https://learn.microsoft.com/en-us/azure/event-grid/cloud-event-schema) events from [Event Grid Namespace topics](https://learn.microsoft.com/en-us/azure/event-grid/concepts-event-grid-namespaces) over HTTP. |
| `namespace_pull` | Namespace | Pull | Pulling events from an Event Grid Namespace topic with lock-token ack/reject — enables dead-lettering and batched consumption. |

All three modes subscribe with the **CloudEvents 1.0** schema (`eventDeliverySchema: CloudEventSchemaV1_0`), so basic, namespace push and namespace pull deliveries share the same payload parser.

## How to use

### Configure the Azure Service Principal

Select an existing **Azure Service Principal** resource or create a new one. The resource provides the credentials Windmill uses to manage subscriptions and (for pull mode) to receive events:

- `azureTenantId`
- `azureClientId`
- `azureClientSecret`

The Azure subscription ID is not a field on the resource — Windmill extracts it at runtime from the ARM path of the topic or namespace you pick as the trigger scope.

The service principal must have enough permissions on the target scope (topic, namespace, or domain) to:

- List topics, system topics and namespaces
- Create and delete Event Grid subscriptions
- For `namespace_pull`: call the `:receive`, `:acknowledge`, and `:reject` data-plane endpoints on the namespace topic

The built-in role **EventGrid Contributor** covers the control-plane operations; **EventGrid Data Sender/Receiver** covers the namespace data-plane calls. Refer to the [Azure Event Grid security and authentication](https://learn.microsoft.com/en-us/azure/event-grid/security-authorization) documentation for the complete list.

### Select the scope resource

The editor auto-loads the resources the service principal can access:

- **Basic edition**: pick a custom topic or system topic from the list (system topics are tagged with `(system)`).
- **Namespace edition**: pick an Event Grid Namespace, then pick a topic inside it.

Click **Refresh** if you just created a topic or namespace and it hasn't appeared yet.

### Subscription name

Windmill creates (or reuses) an Event Grid subscription named according to the **Subscription name** field. The name must be 3–50 characters, letters, digits, and hyphens only. If left empty, Windmill auto-generates one in the form `windmill-{workspace}-{trigger_path}` (truncated to 50 chars).

> A subscription name is unique per `(subscription_name, scope_resource_id, workspace_id)` — two triggers cannot claim the same subscription on the same scope.

### Push endpoint (push modes only)

For `basic_push` and `namespace_push`, Windmill registers the subscription's webhook URL as:

```
{base_endpoint}/api/azure/w/{workspace_id}/{trigger_path}
```

Example: a trigger at `u/alice/cool_trigger` in workspace `demo` becomes:

```
{base_endpoint}/api/azure/w/demo/u/alice/cool_trigger
```

Windmill handles both Event Grid handshakes automatically:

- The classic `SubscriptionValidationEvent` handshake (basic Event Grid)
- The CloudEvents 1.0 `OPTIONS` abuse-protection handshake (namespace push)

Push deliveries are authenticated with a server-managed shared secret. Windmill stores only the sha256 hash; the plaintext is sent to Azure once during subscription create/update (Azure stores it as `isSecret: true` on the delivery attribute and attaches it to each delivery in an `X-Windmill-Secret` header). The secret is regenerated on every save of the trigger.

### Event type filters (optional)

Restrict the trigger to specific event types (one per line) — for example `Microsoft.Storage.BlobCreated` or `Microsoft.Resources.ResourceWriteSuccess`. Leave empty to receive every event delivered to the subscription.

### Choose the runnable

Select the [script](../../script_editor/index.mdx) or [flow](../../flows/1_flow_editor.mdx) to execute when events are received.

### Delete behavior

When you delete a trigger, the editor offers an **Also delete Azure subscription** toggle. Leave it on to clean up the Event Grid subscription in Azure; turn it off to keep the Azure side in place (for example, when handing the subscription over to another trigger).

## Implementation examples

Windmill delivers the event's CloudEvent `data` field to your runnable as `payload`, passed through unchanged. For binary CloudEvents (where `data_base64` is set instead of `data`), `payload` is the base64 string and decoding is the script's job.

### Basic script

```typescript
export async function main(payload: any) {
  // `payload` is the CloudEvent `data` field — typically a JSON object whose
  // shape depends on the event source (Storage, Resource Manager, Key Vault,
  // a custom topic, etc.). See the Azure docs for per-source schemas.
  console.log('Event data:', payload);
  return { processed: true };
}
```

### Using a preprocessor

If you configure a [preprocessor](../../core_concepts/43_preprocessors/index.mdx), you can extract fields before they reach the main function. The preprocessor receives an `event` object with the full CloudEvent envelope alongside the `payload`.

#### Azure Event Grid trigger object

- `payload`: CloudEvent `data` field (usually a JSON object), or the `data_base64` string for binary CloudEvents
- `id`: CloudEvents `id`
- `source`: CloudEvents `source`
- `type`: CloudEvents `type` (e.g. `Microsoft.Storage.BlobCreated`)
- `subject`: CloudEvents `subject`
- `time`: CloudEvents `time`
- `specversion`, `datacontenttype`, `dataschema`: optional CloudEvents attributes when the event sets them
- `delivery_type`: `"push"` or `"pull"`
- `headers`: HTTP request headers (push modes); minimal map in pull mode
- `lock_token`: lock token used to ack/reject the message (pull mode only)
- `trigger_path`: path of the trigger that received the event (push modes only)

```typescript
export async function preprocessor(
  event: {
    kind: 'azure',
    payload: any,
    id: string,
    source: string,
    type: string,
    subject?: string,
    time?: string,
    specversion?: string,
    datacontenttype?: string,
    dataschema?: string,
    delivery_type: 'push' | 'pull',
    headers?: Record<string, string>,
    lock_token?: string,
    trigger_path?: string,
  }
) {
  if (event.kind !== 'azure') {
    throw new Error(`Expected azure trigger kind got: ${event.kind}`);
  }

  return {
    eventType: event.type,
    subject: event.subject,
    data: event.payload,
  };
}

  console.log('Subject:', subject);
  console.log('Data:', data);
}
```

## Testing with capture

The trigger editor includes a **Capture** button that listens for real events without running the runnable. Windmill creates a companion subscription suffixed with `-wm-capture` (subscription names are truncated to 39 chars before appending the suffix, so they stay within the 50-char Azure limit). The captured payloads can then be applied as script or flow arguments, or used to generate a schema.

## Troubleshooting

- **Permission denied on topic/namespace listing**: the service principal is missing `Microsoft.EventGrid/*/read` on the subscription or resource group. Grant **EventGrid Contributor** or equivalent and refresh.
- **Handshake fails on push**: verify the webhook URL matches `{base_endpoint}/api/azure/w/{workspace}/{trigger_path}` and that the instance is reachable from Azure. For namespace push, the CloudEvents `OPTIONS` abuse-protection handshake must land on the same route.
- **`namespace_pull` not receiving events**: confirm the service principal has the **EventGrid Data Receiver** role on the namespace, and that the subscription actually exists (check the Azure portal).
- **Subscription name validation error**: names must match `[A-Za-z0-9-]{3,50}`.

## Error handling

Azure triggers support local error handlers that override workspace error handlers for specific triggers. See the [error handling documentation](../../core_concepts/10_error_handling/index.mdx#trigger-error-handlers) for configuration details and examples.
