{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Aperture Integration","description":"Control the power of AI Agents in runtime.","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"aperture-integration","__idx":0},"children":["Aperture Integration"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Connect ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://tailscale.com/docs/aperture"},"children":["Aperture"]},", Tailscale's AI gateway, to Capsule Security for identity-attributed visibility into every LLM request and inline blocking of prompts that violate policy."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"overview","__idx":1},"children":["Overview"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Aperture calls external webhooks, called hooks, at defined points in each request. This integration registers two Capsule hooks:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Hook"},"children":["Hook"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Aperture event"},"children":["Aperture event"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Type"},"children":["Type"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["capsule"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["entire_request"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Observation"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The prompt, model response, tool calls, and estimated cost, sent after the request completes"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["capsule-guard"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pre_request"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Blocking"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The user prompt, sent before it reaches the model provider - Capsule allows it or blocks it on policy violation"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Aperture authenticates every request with a Tailscale identity, so activity is attributed to the real user:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Each Aperture user (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["login_name"]},") becomes an agent in the Capsule inventory, named ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["<login_name>'s Aperture agent"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The models the user calls and the tools those models invoke become resources linked to that agent"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Each request produces activities for the user prompt, the model response, and each tool call, grouped into sessions by Aperture's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["session_id"]},". Estimated cost is kept with the activity's raw data"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Only users whose Aperture grant includes the Capsule hooks are monitored."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"prerequisites","__idx":2},"children":["Prerequisites"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Before you begin, ensure you have:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["An ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Aperture"]}," deployment and permission to edit its configuration"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Capsule Security"]}," account with admin access"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Network access from Aperture to your Capsule endpoints"]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-1-connect-aperture-in-capsule","__idx":3},"children":["Step 1: Connect Aperture in Capsule"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Log in to the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Capsule Security"]}," portal."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Navigate to ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Integrations"]}," and locate ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Aperture"]},"."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Click ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Connect"]},". The wizard walks you through the hooks snippet, then a grant snippet, then verification, with your tenant's endpoints and token already filled in."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The token is embedded in the hooks snippet, is valid for 365 days, and is shown once, so copy the snippet before moving on."]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-2-add-the-capsule-hooks","__idx":4},"children":["Step 2: Add the Capsule Hooks"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["From a device joined to the tailnet, open the gateway's MagicDNS address, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["http://<aperture-hostname>.<tailnet>.ts.net"]}," (the hostname is the gateway's machine name in the Tailscale admin console under Machines). In the Aperture dashboard, go to ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Administration > Configuration"]}," (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["http://<aperture-hostname>/admin/settings/config"]},"). This opens a raw HUJSON editor holding your whole configuration, including ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["hooks"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["grants"]},"."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Paste the hooks snippet from the Capsule wizard right after the opening ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{"]}," of the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["hooks"]}," object. New gateways ship with a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["hooks"]}," section; if yours has none, add ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"hooks\": {},"]}," first. The snippet holds only the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["capsule"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["capsule-guard"]}," entries, each with a trailing comma, so it pastes cleanly. The result looks like this:"]}]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"hooks\": {\n    \"capsule\": {\n      \"url\": \"<events-endpoint>\",\n      \"apikey\": \"<token>\",\n      \"authorization\": \"bearer\",\n      \"timeout\": \"10s\"\n    },\n    \"capsule-guard\": {\n      \"url\": \"<pre-request-endpoint>\",\n      \"apikey\": \"<token>\",\n      \"authorization\": \"bearer\",\n      \"timeout\": \"5s\",\n      \"fail_policy\": \"fail_open\"\n    }\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The Capsule wizard fills in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["<events-endpoint>"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["<pre-request-endpoint>"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["<token>"]}," for you - the endpoint hosts depend on your region and environment."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["With ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authorization"]}," set to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bearer"]}," (the default), Aperture sends ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["apikey"]}," as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorization: Bearer <apikey>"]},". See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://tailscale.com/docs/aperture/how-to/build-custom-webhook"},"children":["Build a custom webhook"]}," and the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://tailscale.com/docs/aperture/configuration"},"children":["configuration reference"]}," for the full hook schema."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-3-attach-the-hooks-to-a-grant","__idx":5},"children":["Step 3: Attach the Hooks to a Grant"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Hooks fire only for requests that match a grant listing them in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["send_hooks"]},". In the same ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Administration > Configuration"]}," editor, paste the grant snippet from the Capsule wizard right after the opening ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["["]}," of the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["grants"]}," array. To monitor only some users, add its ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["send_hooks"]}," list to the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tailscale.com/cap/aperture"]}," entry of their existing grant instead:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"src\": [\n    \"*\"\n  ],\n  \"app\": {\n    \"tailscale.com/cap/aperture\": [\n      {\n        \"send_hooks\": [\n          {\n            \"name\": \"capsule\",\n            \"events\": [\n              \"entire_request\"\n            ],\n            \"send\": [\n              \"user_message\",\n              \"response_body\",\n              \"tools\",\n              \"estimated_cost\"\n            ]\n          },\n          {\n            \"name\": \"capsule-guard\",\n            \"events\": [\n              \"pre_request\"\n            ],\n            \"send\": [\n              \"user_message\"\n            ]\n          }\n        ]\n      }\n    ]\n  }\n},\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This grant has no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["models"]}," key, so it's a floating grant: it only attaches the hooks and doesn't widen model access on its own."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This grant attaches the hooks for every user matched by ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["src: [\"*\"]"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you also attach the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["capsule"]}," hook to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tool_call_entire_request"]},", Capsule de-duplicates events that share a request id."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"how-prevention-works","__idx":6},"children":["How Prevention Works"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For each ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pre_request"]}," hook, Capsule evaluates the user prompt against your prevention policies and responds with either:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{ \"action\": \"allow\" }\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{ \"action\": \"block\", \"status_code\": 403, \"message\": \"<reason>\" }\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Aperture returns a block to the caller in the model provider's error format."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Prevention applies to the prompt only, because ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pre_request"]}," fires before the model call. Model responses and tool calls are observed, not blocked."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"failure-behavior","__idx":7},"children":["Failure Behavior"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fail_policy"]}," on the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["capsule-guard"]}," hook controls what happens when Capsule is unreachable or slower than ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["timeout"]},":"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"fail_policy"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fail_policy"]}]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Behavior"},"children":["Behavior"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Tradeoff"},"children":["Tradeoff"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fail_open"]}," (recommended)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Aperture lets the request through"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Availability first - a Capsule outage never blocks LLM traffic"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fail_closed"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Aperture blocks the request with a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["503"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Enforcement first - Capsule becomes a hard dependency for every request"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Capsule also fails open internally: if prompt analysis errors, the request is allowed. See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://tailscale.com/docs/aperture/how-to/set-up-guardrails"},"children":["Set up guardrails"]}," for the Aperture side of this flow."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-4-verify-the-integration","__idx":8},"children":["Step 4: Verify the Integration"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Send a request through Aperture as a user covered by the grant from Step 3."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Capsule Security"]}," portal, navigate to ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Inventory > Agents"]}," and confirm an agent named ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["<login_name>'s Aperture agent"]}," appears."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Open the agent and confirm the prompt, response, and any tool calls appear in its activity."]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"roll-out-to-all-devices","__idx":9},"children":["Roll Out to All Devices"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Hooks and grants are configured once, on the gateway. Nothing is configured per client."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each AI client (coding agents, IDEs, SDKs) only needs its API base URL pointed at the gateway. Push that one setting through your MDM or the client's managed-settings mechanism."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Devices must be joined to the tailnet for the hostname to resolve."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://tailscale.com/docs/aperture"},"children":["Aperture: per-client setup"]}," for client-specific instructions."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"troubleshooting","__idx":10},"children":["Troubleshooting"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Symptom"},"children":["Symptom"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Cause"},"children":["Cause"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Resolution"},"children":["Resolution"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Aperture logs ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["401"]}," from Capsule"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Wrong or expired token"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Reconnect Aperture in Capsule to mint a new token, then update ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["apikey"]}," on both hooks"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Nothing appears in Capsule"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The grant has no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["send_hooks"]},", or the user is not covered by that grant's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["src"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Confirm the grant lists both hooks and its ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["src"]}," includes the user"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Prompts are never blocked"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No prevention policies configured, or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["capsule-guard"]}," is not attached with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pre_request"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Configure prevention policies in Capsule and check the grant from Step 3"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Requests fail with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["503"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fail_policy"]}," is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fail_closed"]}," and Capsule was unreachable or exceeded ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["timeout"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Check network access to Capsule, raise ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["timeout"]},", or switch to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fail_open"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Config fails validation with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["unknown config key \"grants[N].send_hooks\""]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["send_hooks"]}," placed at the grant's top level instead of inside a capability entry"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Nest ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["send_hooks"]}," inside the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tailscale.com/cap/aperture"]}," entry, as in Step 3"]}]}]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"security-considerations","__idx":11},"children":["Security Considerations"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Protect the token."]}," Anyone holding it can post events to your tenant. Limit who can read the Aperture configuration, and reconnect the integration in Capsule to mint a new token if it is exposed."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Use HTTPS endpoints only."]}," Aperture sends the token on every hook call."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"support","__idx":12},"children":["Support"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For help with this integration:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Email"]},": support@capsule.security"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Include"]},": Your organization ID, integration status, and any error messages from Aperture"]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"references","__idx":13},"children":["References"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://tailscale.com/docs/aperture/how-to/build-custom-webhook"},"children":["Aperture: Build a custom webhook"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://tailscale.com/docs/aperture/how-to/set-up-guardrails"},"children":["Aperture: Set up guardrails"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://tailscale.com/docs/aperture/configuration"},"children":["Aperture configuration reference"]}]}]}]},"headings":[{"value":"Aperture Integration","id":"aperture-integration","depth":1},{"value":"Overview","id":"overview","depth":2},{"value":"Prerequisites","id":"prerequisites","depth":2},{"value":"Step 1: Connect Aperture in Capsule","id":"step-1-connect-aperture-in-capsule","depth":2},{"value":"Step 2: Add the Capsule Hooks","id":"step-2-add-the-capsule-hooks","depth":2},{"value":"Step 3: Attach the Hooks to a Grant","id":"step-3-attach-the-hooks-to-a-grant","depth":2},{"value":"How Prevention Works","id":"how-prevention-works","depth":2},{"value":"Failure Behavior","id":"failure-behavior","depth":3},{"value":"Step 4: Verify the Integration","id":"step-4-verify-the-integration","depth":2},{"value":"Roll Out to All Devices","id":"roll-out-to-all-devices","depth":2},{"value":"Troubleshooting","id":"troubleshooting","depth":2},{"value":"Security Considerations","id":"security-considerations","depth":2},{"value":"Support","id":"support","depth":2},{"value":"References","id":"references","depth":2}],"frontmatter":{"seo":{"title":"Aperture Integration"}},"lastModified":"2026-10-22T19:34:09.338Z","pagePropGetterError":{"message":"","name":""}},"slug":"/guides/aperture","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}