Migrate from Actions V1 to V2
This guide helps existing users understand the shift from Actions V1 (Embedded) to V2 (Webhook), and provides guidance for new users on how to work with Actions V2.
Deprecation Notice Actions V1 APIs are planned to be sunsetted in ZITADEL V5. While a definitive V5 release date is not yet available, this is the final timeline for V1 API removal. Actions V1 will receive no new features, and all new implementations must use V2.
Why Actions Changed from V1 to V2
ZITADEL Actions evolved from V1 to V2 to address fundamental architectural limitations. While Actions V1 executed JavaScript code directly within ZITADEL's runtime, Actions V2 uses external HTTP endpoints.
This shift enables:
- Better Scalability: Distributed, serverless execution
- Greater Flexibility: Support for any technology stack (not just JavaScript)
- Improved Resilience: Isolating custom logic from core ZITADEL services
- Cost Optimization: Pay-per-execution models with serverless providers
- Enhanced Monitoring: Better debugging and observability of custom extensions
The transition represents a move from "embedded extensions" to "true webhooks," aligning ZITADEL with industry patterns (GitHub Actions, Zapier, etc.).
Architecture Deep Dive: V1 vs V2
Actions V1: The Embedded Model
Custom JavaScript code executed directly inside ZITADEL's process. For a complete list of V1 flow types, see Actions V1 Flow Types.
- Context: Embedded goja JavaScript engine
- Risks: Shared memory space; errors could affect core functionality
- Constraints: Bound to ZITADEL process resources; limited timeout configuration
- V1 Triggers: Pre-event hooks (fire before operation) and Post-event hooks (fire after success)
Actions V2: The Webhook Model
Custom logic lives in external HTTP endpoints called via webhooks.
- Context: Runs on your infrastructure (Cloudflare Workers, AWS Lambda, etc.)
- Isolation: Completely isolated memory; failures do not crash ZITADEL
- Scaling: Endpoints scale independently of ZITADEL
V2 Execution Types:
| Type | Description |
|---|---|
| Request | Triggers when a specific API request occurs |
| Response | Triggers when a specific API response occurs |
| Function | Triggers when specific functionality is used (useful for adding custom claims to tokens or executing external code during OIDC/SAML flows) |
| Event | Triggers reactively when ZITADEL events occur |
Key Architectural Differences
| Aspect | Actions V1 | Actions V2 |
|---|---|---|
| Execution Environment | Embedded in ZITADEL (sandboxed JavaScript) | External webhooks (Cloudflare Workers, AWS Lambda, etc.) |
| Code Location | Inline in ZITADEL Console | Hosted on your infrastructure |
| Language | JavaScript (limited runtime) | Any language |
| API Access | ctx and api objects provided by ZITADEL | ZITADEL REST/gRPC APIs via HTTP calls |
| Triggers | Flow + Trigger (e.g., "Pre Access Token Creation") | Specific API methods or webhook events |
| Dependencies | Limited (zitadel/http, zitadel/log) | Any framework |
| Security | Managed by ZITADEL | HMAC signature verification required (to validate incoming webhook requests from ZITADEL) |
| Scalability | Limited by ZITADEL instance | Independent scaling |
Business Benefits
- Technology Flexibility: Use your preferred language instead of being locked into JavaScript
- Operational Resilience: Isolates failures to prevent cascading outages
- Cost Optimization: Leverage serverless "scale to zero" pricing for custom logic
- Observability: Integrate with tools such as Datadog and Sentry
When to Use Each V2 Execution Type
We provide a collection of ready-to-deploy Cloudflare Worker examples that integrate with ZITADEL Actions V2. Each script demonstrates how to handle ZITADEL events, signatures, and responses directly from a serverless Cloudflare Worker environment. While the examples are designed for Cloudflare Workers, the code can be adapted to deploy on the platform of your choice (AWS Lambda, Azure Functions, etc.).
| If You Need To... | Use This V2 Type | When It Fires | Example |
|---|---|---|---|
| Add custom token claims | Function (preaccesstoken) | Before access token generated | custom-claims |
| Map roles to permissions | Function (preaccesstoken) | Before access token generated | authorization |
| Add "groups" claim from roles | Function (preaccesstoken/preuserinfo) | Before token/userinfo generated | groups-claim |
| Block login | Function (preuserinfo) + interruptOnError: true | Before userinfo generated | block-login |
| Track last login time | Function (preuserinfo) | Before userinfo generated | set-user-metadata |
| Add custom SAML attributes | Function (presamlresponse) | Before SAML response sent | saml-attributes |
| Migrate users on first login | Request + Response (API methods) | Before/after user lookup & session | jit-users-migration |
| Map IDP attributes | Response (/RetrieveIdentityProviderIntent) | After IDP authentication | idp-mapping |
| Auto-assign roles to new users | Event (user.human.added) | When user created | set-role |
| Forward events to monitoring | Event (any event) | When event occurs | datadog-forwarder |
Detailed Flow Explanations
Flow 1: Internal Authentication
Maps user registration and login triggers to Request (Pre-operation) and Response (Post-operation) execution types.
Example Scripts:
- Block/validate requests: block-login-flow.js
- Add metadata post-creation: metadata.js
Flow 2: External Authentication
Handles IDP logins and JIT (Just-in-Time) provisioning via Response (Intent retrieval) and standard User Creation hooks.
Example Scripts:
- Map IDP attributes: idp-mapping.js
- JIT user migration: jit-user-migration.js
Flow 3: Complement Token
Moves from implicit hooks to specific V2 functions, primarily Function (preaccesstoken).
Example Scripts:
- Add custom claims: custom-claims.js
- Add permissions claim: authorization.js
Flow 4: Customize SAML Response
Moves to the Function (presamlresponse).
Example Scripts:
- Add SAML attributes: saml-attributes.js
Additional Resources
Was this page helpful?