When Zitadel Actions v2 is the right tool for custom login logic
A self-hosted Zitadel instance sometimes has to enforce a rule that its built-in login policies cannot express, such as allowing sign-in only for email domains linked to a customer organization, or assigning a role from an HR system or licensing platform that Zitadel does not hold. Actions v2 webhooks are the mechanism for that logic: Zitadel calls an endpoint you host during or after a flow, and that endpoint returns a decision or data that Zitadel applies. The rule lives in code you own, in your infrastructure, while Zitadel keeps the login flow and token issuance. Before you build anything, write the rule in one sentence, name the data it needs, and confirm that no existing policy setting already covers it.
Actions v2 covers three patterns that this topic depends on. Request and response hooks run inside a flow, so your endpoint can inspect what Zitadel is about to do, accept or refuse it, or add data to the result. Functions let you run small pieces of logic that shape user or token data during that flow. Event reactions run after identity events have been recorded, which suits synchronization, notifications and enrichment that do not need to block sign-in. Confirm the exact hook points, payload fields and response formats in the documentation for the release you deploy, because a team that builds against the wrong hook will find the rule runs at the wrong moment.
Choose Actions v2 over changing the application when the rule must hold for every application that signs users in through the same Zitadel organization or project, or when you cannot change those applications because they are third-party products or belong to other teams. Central enforcement means one check protects every client, so an auditor reviews one control point across several codebases. Keep the rule in the application when it reflects that application's own workflow, such as which screen a user sees after sign-in, because a login hook is the wrong place for product behavior. A useful test is scope: if removing the rule from one app would leave a gap in another, the rule belongs in the identity layer.
The main risk is availability. When a hook is synchronous, your endpoint sits in the login path, so an outage, a slow response or an expired certificate can block users who have done nothing wrong. Decide before launch how the hook fails: whether a timeout denies sign-in or allows it with logging, and who is paged when it happens. Authenticate calls from Zitadel to your endpoint, keep the endpoint close to the instance on the network to reduce latency, and load test it at the peak sign-in rate you expect. Also confirm how Actions v2 runs alongside Actions v1 in your release, including which runs first when both apply to the same flow.
Costs fall into three groups. You pay for the engineering to design, build and test the endpoint, for the infrastructure that runs it with its own monitoring, and for change control each time a Zitadel upgrade alters a hook. Those costs recur for as long as the rule exists. When you request a quote from a partner, ask that the written scope name the hooks, data sources, failure behavior, test environments and upgrade checks, and ask whether any Zitadel support agreement covers your custom code. The Enterprise plan on Zitadel's pricing page offers custom support agreements, but the scope of that support for code you write should be confirmed in writing.
Which Actions v2 hook fits each login requirement
This table compares the Actions v2 hook types available for custom login logic, with the typical use, whether each type can change the outcome of a flow, and the main risk. Read the outcome column first, because it determines where a control must sit to block access. Exact hook names and supported triggers vary by version, so confirm them in the Zitadel documentation for the release you deploy before you design the flow.
| Hook type | Typical use | Can it change the outcome? | Main risk |
|---|---|---|---|
| Request hook | Domain checks or blocking a sign-in before the flow continues | Yes. It can deny or modify the request before it is processed. | A slow or failing external call adds latency to every login that passes through it. |
| Response hook | Adding or reshaping data in a response, such as claims that downstream applications read | Yes, but only for the fields the response carries. Confirm which fields are writable. | Changes can reach tokens and downstream applications, so a mistake spreads beyond the login. |
| Function | Reusable logic called from a flow, such as a role lookup from an external provider | Depends on the trigger and flow. Confirm this in the documentation for your version. | Logic that passes testing can fail when a provider times out or returns unexpected data. |
| Event reaction | Metadata enrichment or downstream sync after a user, grant or login event is recorded | No. It runs after the event, so it cannot change that event's result. | Failures do not block the login, so drift between systems can go unnoticed without monitoring. |
| Domain checks | Restricting sign-in or sign-up to approved email domains for an organization | Best placed in a request hook, because access must be decided before it is granted. | Checks that trust an unverified email value can admit users the organization did not intend. |
| Role assignment from an external provider | Mapping groups from an external provider into project roles | Usually a function or request hook, depending on when roles must exist in the token. | Roles granted from stale or partial provider data can persist after the source changes. |
| Metadata enrichment | Adding attributes such as department or cost center to a user or token | Possible in a response hook or event reaction, depending on whether the value must be in the token. | Writing metadata from an event can re-trigger the same event and create a loop. |
| Actions v1 overlap | Existing v1 logic that already runs on the same trigger as a planned v2 hook | Confirm the execution order of v1 and v2 for each trigger before adding logic. | Two systems changing the same flow can produce results that are hard to trace. |
How Zitadel Actions v2 webhooks run during login
Each Actions v2 hook in a Zitadel login flow sends a request to an HTTP endpoint that your team hosts, and Zitadel applies whatever comes back to the flow. The key distinction is whether Zitadel waits. Request and response hooks pause the login until the endpoint answers, so the result can change what the user sees, such as allowing or denying sign-in or adding claims to the token. Event-driven reactions are notified after something has happened and do not hold the login open. Before you design any check, confirm in the current Zitadel documentation which hook types are synchronous, which fields the endpoint can change, and whether the response is signed or otherwise verifiable. The hook type determines where failures land, so these answers are the basis for the rest of the design.
Your team owns the endpoint end to end: the code, the runtime, the certificates, the secrets it uses to call external providers, and its logs. DeployOpen can build and operate that service inside your cloud account, data center or air-gapped network, but the trust boundary stays with your organization. Place the endpoint in a network segment that the Zitadel deployment can reach over TLS and that other workloads cannot reach freely. If the hook also calls a role source or a domain registry, that outbound path is a second dependency and needs its own review. Confirm with the vendor documentation how Zitadel authenticates its calls to your endpoint, which headers or signatures you should verify, and whether the endpoint must be reachable from every Zitadel node or only from specific components in a Kubernetes deployment.
Timeouts and failure behavior decide both the user experience and the security posture of the login. Confirm three things in the current documentation: the timeout Zitadel applies to a synchronous hook and whether you can change it, what happens to the login when the endpoint times out, returns an error or returns a malformed response, and whether Zitadel retries a failed call. These answers determine whether a failed domain check denies sign-in (fail closed) or lets the user through without the added claims (fail open). Many teams choose fail closed for security-relevant checks such as blocking disallowed email domains, and reserve fail open for enrichment that is safe to omit. Test each path in staging by stopping the endpoint, slowing it beyond the configured limit and returning invalid data, then record what the user sees and what the audit trail shows.
Before the build starts, write a short sequence for each hook that shows the call order, the latency your login path can absorb and the fallback decision. Keep the synchronous latency budget small, since every waiting hook adds time to each sign-in, and measure it under peak sign-in load rather than in a quiet test environment. Decide how Actions v2 hooks and any existing Actions v1 functions interact on the same flow, and confirm the execution order in the current documentation before relying on it. Record the failure decision for each hook and have the security owner approve it. These written decisions become the test cases for verification and the runbook entries for operations, so later changes to a hook pass through the same review.
Running Actions v1 and Actions v2 side by side
This table compares the two Actions generations on purpose, configuration, execution, failure handling and maintenance, then lists what a team must settle before both run together. Read each row as a question to answer for your own login flows. The main takeaway is that v2 moves logic into services you operate, so retiring v1 depends on testing those services under live login traffic. Confirm the migration path and deprecation timing in the Zitadel release notes for the version you run.
| Area | Actions v1 | Actions v2 |
|---|---|---|
| Purpose | Scripts attached to flow triggers that run inside Zitadel during login and token steps | Webhook-style targets called on request, response and event conditions, with logic hosted outside Zitadel |
| Configuration scope | Set on flow triggers; confirm which levels your version supports for each flow | Targets and execution conditions defined separately, then attached to specific APIs or events |
| Logic that moves | Domain checks, role assignment and metadata enrichment currently held in v1 scripts | The same logic rebuilt as an endpoint with a defined input and output contract, tested before cutover |
| Execution order | Follows the trigger sequence for each flow; confirm the order for your version in testing | Order across several matching executions must be tested, since it is not assumed from configuration alone |
| Failure behavior | A script error can fail the login step, so review error handling in each v1 script | An unreachable endpoint or timeout can block the request; set timeouts and a defined failure response |
| Maintenance | Code is maintained inside Zitadel and changes follow the console and release cycle | Your service needs its own deployment, versioning, certificates, logging and on-call coverage |
| Retiring v1 | Disable one flow at a time after its v2 equivalent passes tests in staging and production-like traffic | Keep each target live until logs show no remaining dependency on the v1 script it replaces |
How to set up Zitadel Actions v2 in a test instance
This sequence takes one custom login rule from written scope to a tested result in a non-production Zitadel instance. Each step ends with a check, so the team knows whether the endpoint, the hook and the failure behavior work before anything reaches production. The output is a verified rule, its test evidence and the configuration to repeat in production.
Scope the rule and its decision
Your security or identity lead writes down one rule before any code is written. The document names the trigger (for example, a login or token event), the input the endpoint receives, the decision it returns, and the behavior when it cannot answer. Illustrative example: deny sign-in for users whose email domain is outside an approved list. The team also names the owner who signs off on the failure policy. Done when the rule fits on one page and the owner agrees that a timeout or error must deny access, allow access, or fall back to a stated path.
Build and host the endpoint in test
The development team builds a small HTTPS service that accepts the payload Zitadel sends, applies the rule, and returns the response format the hook expects. Authenticate requests from Zitadel with a shared secret or signature check, and keep that secret in your secrets manager and out of source control. Test the endpoint with recorded sample payloads for allow, deny and malformed cases. Done when every sample returns the documented response and the endpoint logs each decision.
Confirm the endpoint is reachable from Zitadel
The platform engineer checks the network path from the Zitadel deployment itself, not from a laptop. Run a test request from the same network segment or container runtime that hosts Zitadel. Confirm that DNS resolves, the certificate chains to a trusted authority, and egress rules or network policies allow the call. If Zitadel runs in Kubernetes, check the namespace network policy as well. Done when the request succeeds from inside the Zitadel environment and a deliberately wrong hostname fails in the expected way.
Configure the hook in a non-production instance
The Zitadel administrator creates a target that points to the tested endpoint in a non-production instance, then adds an action that binds the target to the chosen trigger. Confirm the exact trigger names, payload fields, timeout settings and failure options in the documentation for the Zitadel version you run, since these details can change between releases. Record each setting so the production configuration can be built from the same record. Done when the action shows as active in the Console and the target displays the endpoint URL you tested earlier.
Set failure behavior to match policy
Choose what happens when the endpoint times out, returns an error or returns an unexpected response. The choice must match the written policy from the first step. Some decisions should fail closed so that sign-in is refused. Others may fall back to a lower-risk path that your team has approved. Test the setting by stopping the endpoint or pointing the target at an unused port. Done when a sign-in attempt during the outage behaves exactly as the policy states and the policy owner signs off on the result.
Verify a successful and a denied login
Create two test users in a test organization: one that should pass the rule and one that should fail it. Sign in with the first user and confirm that the expected claim, role or metadata appears in the token or session, as shown by the test application. Sign in with the second user and confirm that access is refused and no session is issued. Done when both outcomes match the expected results and the Console event viewer or the Events API records both attempts.
Verify behavior when the endpoint fails
With the endpoint deliberately unavailable, attempt a sign-in with the user who should pass. Confirm that the login result matches the failure behavior set earlier, and that the failure is logged with enough detail to investigate without exposing personal data. Restore the endpoint and repeat the successful login to confirm recovery. Done when the failed call produces the policy outcome both during and after the outage, and the test evidence is saved with the rule document for review before any production change.
Security and operational risks of custom login hooks
A custom login hook places a service you operate on the path of every sign-in it covers. The cards below set out the risks a security lead will raise and the control to check for each one, so the decision to build hooks is made with the failure modes in view.
External endpoint in the login path
Each Actions v2 call from Zitadel to your endpoint is a network dependency during authentication. Anything the endpoint can reach, or any compromise of it, can influence who gets in and which claims they receive. Control: expose the endpoint on a dedicated hostname, enforce TLS on every call, restrict network paths to the Zitadel instance, and keep a documented list of which hooks call which endpoints. Confirm the outbound network requirements for your Zitadel version before you design the network path.
Secrets and signing for hook calls
The endpoint needs to trust that a request came from your Zitadel instance, and your team needs to trust the response it receives. That depends on shared secrets or signing keys that must be generated, stored outside source control and rotated without a sign-in outage. Check how the Actions v2 target is configured for authentication and whether signature verification is supported in your release, then confirm the rotation procedure before go-live.
Least-privilege access for the hook service
A hook that enriches metadata or assigns roles usually needs to read identity data, and sometimes to write it. Give it a dedicated service account with only the Zitadel API permissions its function requires, and keep administrative tokens out of the hook runtime. Review that grant whenever the hook's logic changes, because permissions tend to grow one ticket at a time.
Logging of hook decisions
When a hook denies a login or changes a token claim, security and support teams need a record they can search later. Log the hook name, version, organization or user identifier, outcome and latency, and keep secrets, full tokens and unnecessary personal data out of the log. Check how hook events reach your log platform or SIEM before you rely on them during an investigation.
Sign-in availability during slow or failed calls
If the endpoint is slow or unavailable, users may be unable to sign in, and the failure often appears first in support queues. Decide for each hook whether a failed call blocks the login or lets it proceed without the hook's result, set timeouts that match your sign-in service expectations, and test both paths. Confirm how Zitadel handles timeouts and errors for Actions v2 in the release you deploy.
Change control for hook code
A hook runs inside the authentication path, so a faulty release affects every user it covers. Keep hook code in version control, require review, and test each change in a non-production instance before it reaches production. Record which version is live, and agree a rollback procedure with the owners of the endpoint before the first production change.
How to test and monitor login logic before and after go-live
The test plan should be written and run before go-live against a non-production Zitadel instance on the same release you will operate. Hook behavior can change between releases, so record the exact Zitadel version and Actions v2 target you tested. Start with a matrix that lists each hook, such as a domain check on the request side, a role assignment after authentication, or metadata enrichment on the response side, and the expected result for allowed and denied users. Each row should name the organization, the identity provider used for federated sign-in, and the project grants the user should or should not receive. Repeat the matrix after every upgrade and every change to a hook function.
Negative cases deserve as much time as the happy path. Send malformed or missing input to each hook endpoint: absent claims, empty or unexpected email domains, unusual characters, oversized metadata, and requests signed with a wrong key if your endpoints verify signatures. Then test endpoint timeouts by slowing or stopping the service and observe what the login flow does. The key question is whether the flow fails closed or lets the user through without the check. Confirm this behavior in your release before go-live, because a domain check that fails open can grant access to the wrong users. Finally, run concurrent logins at your expected peak volume and look for race conditions in role assignment and duplicate writes to user metadata.
Production monitoring starts with knowing where hook outcomes appear. Confirm with Zitadel which logs, events and status signals your version exposes for hook invocations, including whether failures, timeouts and rejected responses are recorded separately from successful calls. Also confirm whether request identifiers can be matched between Zitadel's records and your own endpoint logs. Forward these records to the team's log platform and build alerts on the failure rate of each hook, in addition to overall login errors, because a broken domain check can look like ordinary denial. Track latency for each external endpoint, and tag every outcome with the hook and Actions version that produced it so that Actions v1 and v2 behavior can be separated while both run side by side.
On-call staff need a short runbook written with the security owner before launch. Define who can disable a single hook or roll back to a previous function version, and confirm whether that action requires Zitadel administrator access in your setup. For each alert, list the first checks: endpoint health, recent deployments, the secret or key used to sign requests, and a sample of affected users and organizations. Decide in advance whether a failing hook should block sign-in or allow it with a reduced role set, and document that decision. Rehearse the runbook once after go-live, and record every hook change in your change management system so responders can see what changed before a failure.
Limits and what to confirm in your environment
Actions v2 behavior depends on the Zitadel release you run, how hooks fail and how your deployment routes traffic. The six items below are the questions to answer from the documentation and a test instance before you commit to a hook design. Each one changes either what a hook can do or where it must run.
Hook types and trigger points
Which hook types and trigger points does your chosen release expose for Actions v2, and which of them fire during the login flow you need? Confirm this in the release notes and API reference for the version you plan to run, such as v4.19.5 released on 15 October 2026, then reproduce each trigger on a test instance. A hook that fires after tokens are issued cannot change the outcome of a login, so the trigger point matters as much as the hook type.
Timeout and retry behavior
What happens when your endpoint is slow, returns an error or times out? Ask whether Zitadel retries a failed call, how many attempts it makes, how long it waits for a response, and whether the login fails closed or proceeds without the hook's result. Test this with a deliberately slow endpoint on a test instance. The answer decides whether an outage in an external directory locks out users or lets them through with incomplete claims.
Payload format and size
What does Zitadel send to your endpoint, and how large can the request and response payloads become? Check the documented fields for each hook type, any size limits on requests and responses, and how user metadata and role grants appear in the body. Large group memberships or many metadata keys can exceed limits that a small test user never reaches, so measure with a representative user profile before you finalize the parsing logic.
Interaction with login policies and MFA
Where does a hook run relative to password checks, passkeys, second factors and lockout counters? Confirm whether a hook can see which authentication methods the user already completed, whether it can change the required factors, and whether a rejected hook counts toward lockout thresholds. Settle this before writing domain checks, because a hook placed before MFA and one placed after it can enforce different rules for the same user.
Upgrade impact on existing hooks
Will your hooks keep working after you move to a later Zitadel release? Read the upgrade notes for changes to hook payloads, deprecated trigger points and the way Actions v1 and Actions v2 run side by side. Keep a test instance on the target version, replay each hook against it, and record the results so the upgrade has a clear pass or fail baseline for every hook in production.
Endpoint placement in your topology
Does your deployment method change where the hook endpoint must run? Compare how Zitadel is deployed with Docker Compose, a binary install, or Kubernetes with the official Helm chart, and confirm which network paths Zitadel can reach from its containers or pods. An endpoint that answers from a workstation may be unreachable from the cluster, and an in-cluster service may need network policy changes and certificate trust configured.
Questions buyers ask before building Actions v2 logic
What drives the cost of a custom Actions v2 hook?
The number of rules, the external systems each rule calls, and the failure design the hook needs drive cost most. A hook that checks an email domain against a fixed list is a small piece of work. A hook that calls an HR system, maps roles from a partner directory and handles timeouts, retries and partial data takes considerably more effort.
Cost components to ask about:
- Design and rule logic
- Integration work for each external system
- Test harness and staging environment
- Upgrade planning and regression testing
Upgrade planning adds effort because hook behavior depends on Zitadel's API surface and on the external systems each hook calls. DeployOpen issues a quote from a written scope, so the scope document sets the cost.
How long does a scoped Actions v2 build take?
Duration follows the rules and integrations in scope, not the Zitadel installation alone. A single request hook with no external calls is usually the shortest path to a tested result. Hooks that read from an external provider, write metadata back or need a test harness that replays login flows take longer, because the team must agree on the data contract and the failure behavior first.
DeployOpen does not publish timelines. The written scope sets milestones and dates before work starts. To check a proposed timeline, ask which rules are in scope, which external systems need test access, and which environment the hooks will be promoted through. Also ask which dependencies could pause the build, such as vendor API access approvals.
What does our team need to provide for a hook build?
Three inputs decide whether the build starts on schedule: identity data, access to external systems and a named owner for the endpoint.
- Identity data: the attributes, claims and sample users the hook reads or changes, including which organizations and identity providers are involved.
- External system access: test credentials, network paths and a sandbox or staging instance where the hook can call the provider safely.
- Endpoint owner: the person who accepts the hook's code, its availability expectations and its change process after handover.
Without a named owner, hooks tend to fail quietly after the first upgrade. Confirm these three items before signing the scope.
Can an internal team build Zitadel Actions v2 hooks alone?
Often yes, if the team already operates Zitadel, writes services that accept webhooks and can run a test environment. The work requires skills in OIDC claims, idempotent endpoint design, secret handling and automated testing, plus knowledge of each external system involved.
Internal teams most often struggle with failure design: deciding what a login does when an external call times out, and showing that the behavior holds across releases. Assess the gap by reviewing recent identity incidents and the skills of the people who would own the endpoint. A written scope with DeployOpen can cover the full build, testing and handover, or only a review of an internal design.
How do Actions v1 and Actions v2 hooks run together?
Actions v1 and Actions v2 can run alongside each other, so teams can move hooks over gradually. Confirm how each version handles execution order and errors in the documentation for the Zitadel version you run, since these are the points where mixed setups cause surprises.
Document which hook owns each login decision, and avoid two hooks that both set the same claim. Test the combined login flow end to end, including failure paths. Ask the build team for a table that lists each v1 action, its v2 replacement and the planned cutover date.
What does upgrade planning add to the cost of hooks?
Upgrade planning adds recurring work because every hook depends on Zitadel behavior and on the external systems it calls. The Zitadel release record lists v4.19.5, released 15 October 2026, so new releases should be expected as part of ongoing operation.
Each upgrade should trigger a rerun of hook tests against a staging instance running the same release. Budget for review of changed payloads, updated dependencies and any change to the hook runtime. Ask whether the scope includes one upgrade cycle, how many cycles a retainer covers and who verifies the results.
How do Zitadel plans and support affect a hook project?
Zitadel's plan costs and the cost of a hook build are separate. Zitadel's pricing page lists a Free plan at US$0 per month with 100 daily active users, a Pro plan at US$100 per month including 25,000 daily active users, and an Enterprise plan with custom pricing for self-hosted or cloud deployments.
The Enterprise plan offers custom support agreements and technical account management. DeployOpen does not publish a price list, and a quote follows from the written scope for the hook build. Ask your Zitadel contact whether a support agreement changes who handles hook-related incidents.
How DeployOpen works on this
DeployOpen approaches Zitadel Actions v2 for custom login logic as a scoped engagement inside your own infrastructure. We start from the login decisions you need to enforce, such as domain checks, role assignment from external providers and metadata enrichment, then build, test and document the hooks that run them. Engagements are fixed-scope, with ongoing support available as a retainer.
How we scope
- The written scope lists each login decision the hooks will enforce, such as domain checks or role assignment.
- We record which Actions v1 flows stay in place so both versions can run side by side during transition.
- Engagements are fixed-scope, so scope, deliverables and timelines are agreed in writing before work starts.
How we build
- Hooks are built against the request and response payloads for each login step you have agreed to change.
- External lookups for role assignment and metadata enrichment get timeouts and error handling defined up front.
- Each function and event reaction is tested against a non-production instance before it reaches production.
How we hand over
- Handover includes source for every hook, with configuration kept in version control in your repositories.
- Documentation covers each hook's trigger, inputs, failure behavior and the Actions v1 flows it replaces or joins.
- Your operators walk through the runbook for changing a hook, rolling it back and reading its logs.
How we support
- Ongoing support, upgrades and testing are available as a retainer once the hooks are live.
- Hooks are rechecked when you move to a new Zitadel release, since the Actions interfaces may change between versions.
- Confirm with Zitadel's current documentation which Actions v2 features your target release provides before the build.

