Skip to content

Security and governanceGuide

Anthropic profile restrictions: enforce them at the gateway

Tagging requests with Anthropic user profiles limits who gets punished. It does not stop that customer's traffic to your other providers. This guide builds the loop: verify the webhook, read the profile's current state, and mirror the restriction as a gateway policy.

By INFRO Editorial

Published

10 min read

A grid of dark glass cards with small bar charts, a few of them lit amber, on a dark ground

In short

Register a beta organization webhook for the two enforcement events, verify and deduplicate each delivery, then fetch the profile and apply its current state as a gateway policy. Because Anthropic's page says event delivery isn't live and events can be dropped, a polling reconciler is the part that makes the restriction reliable 3.

Key takeaways

  • Anthropic's organization webhooks are in beta behind the webhooks-2026-10-07 header, need account-team access, and the docs say event delivery isn't live yet, so build the handler but do not rely on events alone.
  • Events carry only IDs: verify the signature, deduplicate on the event id, return 2xx within 5 seconds, then retrieve the profile and act on its current state, not on event order.
  • A scheduled list of profiles filtered by enforcement_status is the reconciler that catches dropped, late, pre-registration and disabled-endpoint cases.
  • Anthropic's restriction applies to Anthropic traffic; mirroring it as a gateway policy is what covers the same customer's requests to other providers.
  • Record the policy you overwrote so a resolved action restores it, and treat a 404 on the profile as deletion, which sends no resolved event.

What the webhook does and does not do

If you already send the anthropic-user-profile-id header on every request, an enforcement action lands on one end customer instead of your whole organization 1. That is attribution. The next question is how fast your own systems learn that a customer was restricted, and what they do about it.

Anthropic added organization webhooks to the Admin API on 20 October 2026, in beta with the webhooks-2026-10-07 header. They cover two event types: one when Anthropic restricts or bans one of your organization's user profiles, and one when that restriction or ban ends. Access is by request through your Anthropic account team 2.

Read this part of the docs before you build anything: the organization webhooks page says event delivery isn't live, that Anthropic doesn't send the two events, and that an endpoint you register receives nothing. It adds that you can register endpoints and build your handler from the documented event format 3. That was the state of the page on 1 November 2026. Re-read it before you rely on events.

So the design below has two inputs into one state machine: webhook events when they arrive, and a polling reconciler that works today. The webhook makes enforcement fast. The reconciler makes it correct.

Register the endpoint

You need an organization on the Claude API with organization webhooks turned on and without HIPAA readiness enabled. You also need an Admin API key, an OAuth bearer token with the org:admin scope, or a personal or service account key that isn't scoped to a workspace. Creating, changing or deleting an endpoint needs the admin, owner or primary owner role 3.

The endpoint URL must be HTTPS on port 443, with a public domain name rather than an IP address, and two enabled endpoints can't share a URL. Anthropic disables an endpoint that returns a redirect, resolves to a non-public IP address, or keeps failing, and it doesn't send skipped events later 3.

register_webhook.py
import anthropic

client = anthropic.Anthropic()  # Admin API key in ANTHROPIC_API_KEY
endpoint = client.beta.organization.webhooks.create(
    url="https://hooks.example.com/anthropic-enforcement",
    name="Enforcement actions",
    enabled_events=[
        "user_profile.enforcement_action_opened",
        "user_profile.enforcement_action_resolved",
    ],
)
print(endpoint.id)            # wep_...
store_secret(endpoint.signing_secret)  # whsec_..., returned only now

The whsec_ signing secret is returned only by the response that creates the endpoint or replaces the secret, so put it in your secret store immediately. If you regenerate it, the old secret stops working at once, and later deliveries, including retries of earlier events, are signed with the new one 3. Add an alert on the endpoint's state: poll GET /v1/organizations/webhooks and page someone if it reads disabled, because a disabled endpoint silently stops delivering and you re-enable it with an update that sends "state": "enabled" 3.

Two more details trip people up. Every request needs the anthropic-beta: webhooks-2026-10-07 header (the SDK sends it), and a missing header returns a 404, not a 400 3. And any request whose anthropic-beta header still carries user-profiles-2026-09-04 or an earlier value gets responses without the enforcement_status and enforcement_actions fields, and the list filter returns a 400. Remove that value from your requests 3.

Verify, deduplicate, acknowledge

Deliveries follow the Standard Webhooks specification, with webhook-id, webhook-timestamp and webhook-signature headers. Verify the signature with the endpoint's secret, using any Standard Webhooks library, before you trust the body. Return any 2xx within 5 seconds and do slow work afterward; anything else counts as a failed attempt. The same event can arrive more than once, with an id equal to the webhook-id header, so deduplicate on it 3.

The event holds IDs and nothing else: data.id is the profile id and data.enforcement_action_id starts with enac_ 3. The handler therefore does very little.

receiver.py
from standardwebhooks import Webhook   # any Standard Webhooks library
from flask import Flask, request, abort

app = Flask(__name__)
verifier = Webhook(load_secret())      # the whsec_ secret

@app.post("/anthropic-enforcement")
def receive():
    body = request.get_data()          # raw bytes: verify before parsing
    try:
        verifier.verify(body, dict(request.headers))
    except Exception:
        abort(400)

    event_id = request.headers["webhook-id"]
    if not seen.add_if_new(event_id):  # unique constraint in your database
        return "", 204                 # duplicate: acknowledge, do nothing

    profile_id = request.get_json()["data"]["id"]
    queue.enqueue("sync_profile", profile_id)  # slow work happens elsewhere
    return "", 204

Verify against the raw bytes, not a re-serialized JSON object, or the signature will not match. Record the event id after verification, not before, so a forged request can't poison your dedupe table. Note what the handler does not do: it never reads the event type to decide what to apply. It only learns which profile changed.

Act on the profile's current state

Anthropic says events might arrive late or out of order, and that you should use the profile's current state, not the order events arrive in 3. It also sends an event only after the profile shows the change, so a retrieve returns that change or a later one. Hence the worker pattern: retrieve, compute, apply.

GET /v1/user_profiles/{id} returns enforcement_status and an enforcement_actions list. Each action has an id, a type of restriction or ban, a reason, opened_at, and expires_at or null; the reference example also shows an action field 34. The status is banned while a ban is open, restricted while any other action is open, and unrestricted when none is. Treat any other value as restricted. After a resolved event the resolved action is gone from the list, and a 404 means the profile was deleted 3.

sync_profile.py
def sync_profile(profile_id: str) -> None:
    try:
        p = client.user_profiles.retrieve(profile_id)
    except anthropic.NotFoundError:
        desired = "deleted"
    else:
        desired = {"unrestricted": "open", "banned": "ban"}.get(
            p.enforcement_status, "restrict")   # unknown values -> restrict

    customer = customers.by_profile_id(profile_id)
    if customer is None:
        alert("enforcement for unmapped profile", profile_id)
        return
    apply_gateway_policy(customer, desired, actions=getattr(p, "enforcement_actions", []))

Deleting a profile ends its open actions without a resolved event 3. Your own account-closure flow deletes profiles, so a deleted outcome should clear the mirrored policy and write an audit note, not leave a block behind for a customer who no longer exists.

Mirror the restriction as a gateway policy

A restriction on a profile is Anthropic's record about Anthropic traffic. The same customer can still send requests to your other providers through the same gateway, and whether that should continue is your decision. For a ban for abuse, most teams want the customer stopped everywhere until a person has looked. For a narrow restriction, they may want to keep serving the customer on another provider or lower the allowance. Decide this per status, in writing, before the first event.

The mapping from Anthropic's profile to your gateway needs one join: your gateway's end-customer id. Anthropic's profile carries a reference_id that you set to your own stable identifier, and it need not be unique 1. If you set it to the same opaque id you send the gateway as the end-customer id, the join is trivial; otherwise keep the table uprof_ -> customer id on the customer record, where Anthropic's guide already says to store the profile id 1.

With INFRO, two shipped features carry this. An end-customer cap rule sets a default per period for every end customer, and an override changes the amount for one customer. An override of 0 refuses every priced request for that customer, and an override applies from the next request 5. The customer is identified by the id your app sends in metadata.user_id, an opaque string up to 128 characters 5. Routing rules on a route can match end_user_id and send those requests to their own targets, with the first matching rule deciding 6.

illustrative rule body
{
  "name": "Restricted at Anthropic",
  "match": "all",
  "conditions": [
    { "field": "end_user_id", "op": "in", "value": ["cust-8f2a1c"] }
  ],
  "targets": [{ "connection_id": "conn_…", "model": "openai/gpt-6-sol" }],
  "fallthrough": false
}

The shape follows the rule example in INFRO's routes docs 6; the customer id, connection and model are placeholders. Because rules are checked in order and the first match decides, keep enforcement rules above ordinary tier rules. Set fallthrough to false so the default targets, which may include Anthropic, aren't tried when your restricted-customer targets fail.

Both writes can come from your sync worker through the management API, which serves end-customer caps and routes with a scoped, expiring token. Per its documentation, PATCH and DELETE need If-Match and POST accepts Idempotency-Key 7, which suits a worker that may run the same sync twice. I did not read the exact override endpoint, so take its path and body from the management API reference rather than from this post.

Restore what you overwrote

When the profile returns to unrestricted, the worker has to put things back. Don't assume the previous value was "no override". A customer may have had a negotiated override of $200 a day. In INFRO, an override of null means that customer is not capped under the rule, which is a different thing from having no override, where the default amount applies 5. If your worker writes null on resolution, it can accidentally uncap a customer who had been under the default.

Store the prior state before the first enforcement write.

Python
def apply_gateway_policy(customer, desired, actions):
    if desired in ("ban", "restrict") and not customer.saved_policy:
        customer.saved_policy = gateway.read_policy(customer.id)   # once
    if desired == "ban":
        gateway.set_cap_override(customer.id, amount=0)
    elif desired == "restrict":
        gateway.add_reroute_rule(customer.id)
    else:  # open or deleted
        gateway.restore(customer.id, customer.saved_policy)
        customer.saved_policy = None
    audit.log(customer.id, desired, [a.id for a in actions])

Writing the desired state, rather than a toggle, makes the function safe to run for every event, retry and reconciler pass. It also leaves a record: INFRO's audit log records policy changes with actor and timestamp, and your own log of the enac_ ids ties each change to Anthropic's action.

The reconciler is the part that has to work

Several situations produce no event at all 3:

  • The action opened before Anthropic turned on delivery for your organization, or before you registered the endpoint.
  • Every delivery attempt failed. Anthropic retries a few times over several minutes, then drops the event.
  • The endpoint was disabled, and Anthropic skips events for a disabled endpoint without sending them later.
  • Delivery isn't live yet, which is the documented state now.

The catch-up path is also documented: list your profiles with enforcement_status[]=restricted and enforcement_status[]=banned. A page can contain fewer profiles than limit, or none, before the list ends, so keep requesting until next_page is null 3. The filter arrived on 21 October 2026 together with the enforcement fields 2.

reconcile.py
def reconcile():
    flagged, page = set(), None
    while True:
        res = client.user_profiles.list(
            enforcement_status=["restricted", "banned"], page=page)
        flagged |= {p.id for p in res.data}
        page = res.next_page
        if page is None:
            break
    for pid in flagged:
        queue.enqueue("sync_profile", pid)
    for c in customers.with_saved_policy():     # mirrored but no longer flagged
        if c.profile_id not in flagged:
            queue.enqueue("sync_profile", c.profile_id)

The second loop matters as much as the first. A missed resolved event leaves a customer blocked in your gateway after Anthropic cleared them, and only a comparison from your side finds it. Run the reconciler at deploy time, on a schedule you can defend, and whenever the endpoint's state changes. The schedule is your exposure window if events never arrive: choose an interval from how long you can tolerate serving a restricted customer, not from API cost.

Limits, and what a gateway cannot cover

  • Traffic outside the gateway. A policy in the gateway constrains requests that go through it. Calls made direct to a provider with a copy of a key bypass it, so keep provider keys out of applications.
  • Header forwarding. Check that the profile id still reaches Anthropic on the path your gateway uses, since the header is what scopes enforcement to one customer 1.
  • Passthrough profiles. A passthrough profile stands for a company, not a person 1. Blocking the company id blocks everyone behind it, so decide whether a restriction should stop the whole company.
  • Beta surface. Both the webhooks and their access are in beta, and the documented contract can change. Pin the beta header in code and re-read the page when you upgrade.
  • Your own users' experience. A refused request should become a message your app can show. INFRO's cap refusal is built to return an error your app can show to that user 5, so map it to your own copy and don't retry it.

Rollout checklist

  • Request organization webhooks access from your Anthropic account team, and confirm HIPAA readiness is off and CMEK isn't set on the default workspace 3.
  • Remove user-profiles-2026-09-04 and earlier values from anthropic-beta on profile requests.
  • Register the endpoint; store the whsec_ secret; alert on state: disabled.
  • Verify against raw bytes, dedupe on webhook-id, return 2xx inside 5 seconds.
  • Map every uprof_ id to the gateway's end-customer id; alert on unmapped profiles.
  • Write the status-to-policy table, including deleted and unknown values.
  • Store prior policy before the first write; restore it, never null.
  • Run the reconciler at startup and on a schedule; test it with a dropped-event drill.
  • Log every change with the enac_ id, and review the audit trail.

Questions readers ask

Does a restriction on a profile stop that customer's requests to OpenAI or Gemini?
No. The profile is Anthropic's record, and the restriction applies at Anthropic. Whether the same customer should be limited elsewhere is your policy decision, which a gateway rule can enforce for every provider behind it.
What should the handler do with an unknown enforcement_status value?
Treat it as restricted. Anthropic's page says to treat any value other than the documented ones as restricted 3.

From the team that builds it

Where this shows up in INFRO

INFRO is a control plane in front of the provider accounts you already hold. The capabilities below are the parts of it this article touches, each with the documentation that defines its behaviour.

  • End-customer spend caps

    Available with a plan

    One spend cap for every end customer of your product, per organization or per project, with overrides your backend sets through the management API. A refused request returns a 402 with the time the cap resets, for your app to show its user. Caps and the per-customer cost view on every plan.

    Read the documentation for End-customer spend caps
  • Routing rules

    Available with a plan

    If-rules on a route send matching requests to their own targets, by metadata, feature, end user, key, project, prompt size, streaming, images, tools or time of day. Failure rules decide per failure kind whether to retry, move on or stop.

    Read the documentation for Routing rules
  • Management API

    Available with a plan

    Configure an organization as code through /manage/v1 with a scoped, expiring management token: projects, gateway keys, connections, routes, budgets, alerts, cost centers, commitments, guardrails and end-customer caps. Reads and writes on every plan.

    Read the documentation for Management API
  • Spend attribution

    Available with a plan

    Group cost by model, project, key, or an end-user id you attach to the request.

    Read the documentation for Spend attribution
  • Audit log

    Available with a plan

    Member, key, and policy changes recorded with actor and timestamp, viewable in the console.

    Read the documentation for Audit log

INFRO accounts are self-serve. Provider usage is billed to you by your providers; INFRO charges a software subscription and takes 0% of model spend. See plans.

Sources

Numbered in order of first citation. Each was opened and checked against the claim on the date shown.

  1. 1.User profiles for end-customer attribution (opens in a new tab) — Anthropic. Accessed Nov 1, 2026.
  2. 2.Claude Platform release notes (opens in a new tab) — Anthropic. Accessed Nov 1, 2026.
  3. 3.Organization webhooks (opens in a new tab) — Anthropic. Accessed Nov 1, 2026.
  4. 4.Get User Profile (opens in a new tab) — Anthropic. Accessed Nov 1, 2026.
  5. 5.End-customer caps (opens in a new tab) — INFRO. Accessed Nov 1, 2026.
  6. 6.Routes (opens in a new tab) — INFRO. Accessed Nov 1, 2026.
  7. 7.Management API (opens in a new tab) — INFRO. Accessed Nov 1, 2026.

About this article

Read on 2026-11-01: Anthropic's Claude Platform release notes, organization webhooks page, user profiles guide and Get User Profile reference, plus INFRO's end-customer caps, routes and management API docs pages. Anything about how Anthropic enforces a restriction on inference beyond those pages, and INFRO override endpoint paths not shown on the pages read, is left out as unconfirmed.

Last checked against its sources on . The next review is due by . No separate named reviewer is recorded for this article.

How articles are written, sourced and corrected is set out in the editorial standards.