Skip to main content

Azure Event Grid triggers

Windmill can connect to Azure Event Grid 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 feature. They are disabled on the Cloud.

Trigger modes

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

ModeEditionDeliveryUse when
basic_pushBasicPushReacting to first-party Azure events from custom topics, system topics (Storage, Resource Manager, Key Vault, Service Bus, IoT Hub control-plane, etc.) or domains.
namespace_pushNamespacePushPushing CloudEvents 1.0 events from Event Grid Namespace topics over HTTP.
namespace_pullNamespacePullPulling 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 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 or flow 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

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, 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)
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,
};
}

export async function main(
eventType: string,
subject: string | undefined,
data: any
) {
console.log('Event type:', eventType);
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 for configuration details and examples.