An Azure service that provides a hybrid, multi-cloud management platform for APIs.
Hi @Alex
Thank you for reaching out to Microsoft Q&A.
The behavior you are observing is not due to an issue with the <choose> policy logic itself, but rather how policy evaluation and routing work internally in Azure API Management Standard v2 (especially with External VNet and persistent HTTP/2 connections). In this architecture, requests are handled by distributed gateway instances, and policies are propagated asynchronously across these instances. During certain conditions (such as configuration refresh or transient desynchronization), a specific gateway node may evaluate the policy using a slightly stale or inconsistent state. Since your client is using persistent HTTP/2, requests can get pinned to a specific gateway instance, which explains why only one operation is impacted while others continue to work. Additionally, using context.Request.Url.Path.Contains(...) introduces fragility because it relies on string-based evaluation after routing has already been resolved by APIM. This makes the condition sensitive to differences in path normalization, API suffix, or internal routing representation, leading to intermittent mismatches and unintended fall-through to the validate-jwt policy (resulting in 401 TokenNotPresent errors). Redeploying the policy forces synchronization across gateway nodes, which is why the issue resolves immediately.
Refer below points to resolve this issue or this is the workaround:
1. Prefer operation-level policy instead of API-level <choose>
Move the webhook logic to the specific operation rather than evaluating it via path matching at API level. This avoids runtime condition evaluation entirely and aligns with APIM’s routing model.
<!-- Apply at operation level -->
<inbound>
<base />
<!-- Skip validate-jwt here -->
</inbound>
2. Avoid using context.Request.Url.Path.Contains (Use operation metadata instead)
Instead of string-based path matching, use routing-aware properties:
@(context.Operation.Name == "WebhookOperation")
This is more reliable because APIM already resolves the operation before policy execution.
3. If path-based logic is mandatory, use OriginalUrl with exact match
Avoid substring checks and use a normalized comparison:
@(context.Request.OriginalUrl.Path.Equals("/api/Webhook/path", StringComparison.OrdinalIgnoreCase))
4. Pre-compute condition using set-variable (Defensive pattern)
Avoid re-evaluation inconsistencies by computing the value once:
<set-variable name="isWebhook"
value="@(context.Operation.Name == "WebhookOperation")" />
<choose>
<when condition="@(context.Variables.GetValueOrDefault<bool>("isWebhook"))">
<!-- skip JWT -->
</when>
</choose>
5. Mitigate gateway stickiness behavior (additional workaround)
- Avoid long-lived HTTP/2 connections for webhook traffic (if possible)
- Test scaling APIM to 2 units to reduce impact of single-node inconsistency
6. Use diagnostics to confirm routing behavior during failure
Capture logs to validate which operation and condition path was evaluated during the issue window.
and click on Yes for was this answer helpful. And, if you have any further query do let us know.