Share via

Standard V2 APIM intermittent policy evaluation failure causing webhook 401s

Alex 45 Reputation points
2026-05-01T04:32:54.5966667+00:00

Hi,

I'm having an issue with APIM where I have a policy 'choose' condition that is intermittently failing to evaluate properly and causing 401 errors in my app.

SKU: StandardV2 (capacity 1), VNet type External

Issue: API-level inbound <choose> with @(context.Request.Url.Path.Contains("...")) intermittently evaluates false when it should match, causing fall-through to validate-jwt → 401 with LastErrorReason: TokenNotPresent. Lasts 60–90 min, recovers on its own, redeploying the policy fixes it instantly.

Scope: One operation only. Other operations on the same API are 100% success in the same window. Single client, persistent HTTP/2.

API-level inbound policy (relevant excerpt):

<inbound>
  <base />
  <set-variable name="rate-limit-key" value="@{ /* extract oid/sub/appid from JWT, fallback to subscription key or 'anonymous' */ }" />
  <rate-limit-by-key calls="10000" renewal-period="60" counter-key='@((string)context.Variables["rate-limit-key"])' />
  <cors>...</cors>
  <choose>
    <when condition='@(context.Request.Url.Path.Contains("/api/Webhook/path"))'>
      <!-- Skip JWT — webhook authenticates via shared secret in backend -->
    </when>
    <otherwise>
      <choose>
        <when condition='@(context.Request.Headers.GetValueOrDefault("CustomAuthHeader", "false") != "false")'>
          <validate-jwt header-name="CustomAuthHeader" failed-validation-httpcode="401" ...>
            <issuer-signing-keys><key .../></issuer-signing-keys>
            <issuers>...</issuers>
          </validate-jwt>
        </when>
        <otherwise>
          <validate-jwt header-name="Authorization" failed-validation-httpcode="401" ...>
            <openid-config url="..." />
            <audiences>...</audiences>
          </validate-jwt>
        </otherwise>
      </choose>
    </otherwise>
  </choose>
  <set-header name="X-User-Token" exists-action="override">
    <value>@(context.Request.Headers.GetValueOrDefault("Authorization",""))</value>
  </set-header>
  <set-backend-service backend-id="..." />
  <authentication-managed-identity resource="..." />
</inbound>

The failing condition is the outer <when>. The inner <choose> and both validate-jwt blocks behave correctly throughout — same gateway, same window, other operations succeed.

Asks:

  1. Known issue on Standard V2 + External VNet where new capacity units serve traffic before policy is fully synced?
  2. Recommended pattern instead of context.Request.Url.Path evaluation in an API-level <choose> on this SKU?
Azure API Management
Azure API Management

An Azure service that provides a hybrid, multi-cloud management platform for APIs.


1 answer

Sort by: Most helpful
  1. Siddhesh Desai 6,555 Reputation points Microsoft External Staff Moderator
    2026-05-01T04:58:44.52+00:00

    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.


Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.