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:
| Mode | Edition | Delivery | Use when |
|---|---|---|---|
basic_push | Basic | Push | Reacting 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_push | Namespace | Push | Pushing CloudEvents 1.0 events from Event Grid Namespace topics 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:
azureTenantIdazureClientIdazureClientSecret
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:rejectdata-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
SubscriptionValidationEventhandshake (basic Event Grid) - The CloudEvents 1.0
OPTIONSabuse-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: CloudEventdatafield (usually a JSON object), or thedata_base64string for binary CloudEventsid: CloudEventsidsource: CloudEventssourcetype: CloudEventstype(e.g.Microsoft.Storage.BlobCreated)subject: CloudEventssubjecttime: CloudEventstimespecversion,datacontenttype,dataschema: optional CloudEvents attributes when the event sets themdelivery_type:"push"or"pull"headers: HTTP request headers (push modes); minimal map in pull modelock_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/*/readon 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 CloudEventsOPTIONSabuse-protection handshake must land on the same route. namespace_pullnot 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.