Build a custom webhook
Last validated:
Aperture can send real-time event data to external services through webhooks to feed LLM usage data into your own audit logs, cost dashboards, security tools, or policy engines. For each request that matches a grant, Aperture POSTs a JSON payload containing the metadata and data types you select.
Prerequisites
Before you begin, ensure you have the following:
- An Aperture instance with at least one provider configured.
- Admin access to the Aperture configuration.
- An HTTP or HTTPS endpoint that accepts POST requests with JSON payloads.
Define a hook endpoint
Open the Settings page in the Aperture dashboard and add a hooks section to your configuration. Each hook has a unique key and specifies the endpoint URL:
"hooks": {
"my-webhook": {
"url": "https://example.com/aperture-events",
"apikey": "YOUR_API_KEY"
}
}
If your endpoint uses a non-Bearer authentication scheme, set the authorization field:
"hooks": {
"my-webhook": {
"url": "https://example.com/aperture-events",
"apikey": "YOUR_API_KEY",
"authorization": "x-api-key",
"timeout": "10s"
}
}
The authorization field supports bearer (default), x-api-key, and x-goog-api-key. The timeout field accepts Go duration strings such as 5s, 30s, or 1m and defaults to 5s.
A hook defined in the hooks section has no effect until a grant references it.
Trigger the hook from a grant
Add a send_hooks entry to a capability in your grants section. This controls which requests trigger the hook and what data Aperture includes in the payload:
"grants": [
{
"src": ["*"],
"app": {
"tailscale.com/cap/aperture": [
{
"models": "**",
"send_hooks": [
{
"name": "my-webhook",
"events": ["entire_request"],
"send": ["estimated_cost"]
}
]
}
]
}
}
]
Select hook events
The events array specifies when Aperture calls the hook:
| Event | Description |
|---|---|
entire_request | Fires for every completed request. |
tool_call_entire_request | Fires once after the response completes if any message in the response contained tool calls. |
Select data types
The send array specifies which data to include in the POST payload:
| Type | Description |
|---|---|
tools | Array of tool calls extracted from the response. |
request_body | The original request body sent to the LLM. |
user_message | The user's message from the request. |
response_body | The reconstructed response body JSON. |
raw_responses | Array of raw SSE messages (for streaming) or single response object. |
estimated_cost | Dollar cost estimate, pricing basis, and token usage breakdown. |
grants | Non-Aperture app capabilities from the user's grants. |
quotas | Current state of all quota buckets that applied to this request. |
Understand the payload format
Every hook call includes a metadata object with request context, regardless of what you specify in send:
{
"metadata": {
"login_name": "alice@example.com",
"user_agent": "curl/8.0",
"url": "/v1/chat/completions",
"model": "gpt-5",
"provider": "openai",
"tailnet_name": "example.com",
"stable_node_id": "n12345",
"request_id": "abc123",
"session_id": "oacc_1a2b3c4d5e6f7890"
}
}
When you include data types in the send array, Aperture adds them to the payload alongside metadata. For example, with "send": ["tools", "estimated_cost"]:
{
"metadata": {
"login_name": "alice@example.com",
"...": "...",
"estimated_cost": {
"dollars": 0.0342,
"cost_basis": "anthropic/claude-sonnet-4-5",
"usage": {
"input_tokens": 1500,
"output_tokens": 800,
"cached_tokens": 200,
"reasoning_tokens": 0
}
}
},
"tool_calls": [...]
}
Verify the webhook
After saving the configuration, send a test LLM request through Aperture and check that your endpoint receives the POST payload. If the webhook does not fire:
- Confirm the hook name in
send_hooksmatches the key in thehookssection. - Confirm the grant's
srcandmodelspatterns match your test request. - Check the Aperture server logs for timeout or connection errors to the hook URL.
To temporarily disable a hook without removing it from the configuration, set "disabled": true in the hook definition.
Next steps
- Export usage data to S3 for long-term storage of session logs.
- Integrate Oso with Aperture or Integrate Cerbos with Aperture for policy engine integrations that use hooks.
- Refer to the hooks configuration reference for the complete field reference and additional examples.