Some tools send events instead of being polled. Govern has two receivers, served by the Govern server before session authentication and authenticated by ingest tokens:
| Connector | Receiver | What sends |
|---|---|---|
claude-code | POST /api/v1/vendor-audit/otlp/v1/logs | Claude Code's OpenTelemetry log events |
gemini-cli | the same | Gemini CLI's OpenTelemetry log events |
codex | the same | OpenAI Codex's OpenTelemetry log events |
webhook | POST /api/v1/vendor-audit/webhook | JSON audit records from any service: Notion, Perplexity Enterprise, an internal gateway, a SIEM forwarder |
Ingest tokens
POST /api/v1/vendor-audit/connectors/claude-code/tokens
{"name": "engineering laptops"}
- Needs
vendoraudit.connectors:manage. The answer is201with the token (kvat_...), shown once, andsetup: the endpoint, the file the settings go in, its format and the settings with the token filled in. - Govern keeps only the token's SHA-256. At most 20 active tokens per connector.
- The token decides the organisation and the connector. A token of one agent's connector never files records under another's, and OTLP and webhook tokens are not interchangeable (
401). GET .../tokenslists tokens (id,name,prefix,created_at,last_used_at,revoked_at,active);DELETE .../tokens/{tokenID}revokes one. Revoking the last active token turns the connector off.- Turning a push connector on needs an active token (
409 ingest_token_required).
Claude Code
- Create a
claude-codetoken. The setup carries the managed settings to deploy:CLAUDE_CODE_ENABLE_TELEMETRY=1,OTEL_LOGS_EXPORTER=otlp,OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/protobuf,OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=<your API URL>/api/v1/vendor-audit/otlp/v1/logsand the token inOTEL_EXPORTER_OTLP_LOGS_HEADERS. Only the logs signal is configured. - Deploy them as Claude Code managed settings, which developers and repositories cannot override.
- Turn the connector on.
Each record becomes one event of type claude_code.<event name> (user_prompt, tool_result, tool_decision, api_request, api_error and others), with the actor from the user's email and account id and the session as the resource.
| Setting | Default | Effect |
|---|---|---|
store_prompts | off | Store prompt and response text, raw API bodies and the system prompt; the setup also asks Claude Code to send them |
store_tool_details | off | Store tool parameters, inputs, output and error text |
anthropic_organization_id | empty | Refuse events from any other Anthropic organisation, for example a developer's personal account |
Withheld attribute names are listed in the stored payload.
Gemini CLI
The token comes with the telemetry block of Gemini CLI's system settings (/etc/gemini-cli/settings.json on Linux, /Library/Application Support/GeminiCli/settings.json on macOS, C:\ProgramData\gemini-cli\settings.json on Windows, which override user settings). The endpoint is <your API URL>/api/v1/vendor-audit/otlp; Gemini CLI appends /v1/logs. Events are gemini_cli.<name>. store_prompts and store_tool_details work as for Claude Code.
OpenAI Codex
The token comes with the [otel] table for Codex's config.toml, exporting to .../otlp/v1/logs over binary protobuf with the token in an Authorization header. otel keys in a project's .codex/config.toml are ignored by Codex, so place it in the user or managed configuration. Events are codex.<name>. Of the per-chunk sse_event records, only response.completed, response.failed and errored ones are stored.
The audit webhook
POST /api/v1/vendor-audit/webhook
Authorization: Bearer kvat_...
Content-Type: application/json
{"events": [{"id": "evt_123", "timestamp": "2026-10-16T12:00:00Z", "type": "page.exported",
"actor": {"email": "alice@example.com"}}]}
- The body is one object, an array of objects, or an object whose
events,records,data,logsorentriesmember is an array: up to 5,000 records, 4 MiB (16 MiB after gzip). - Identity from
id,event_id,eventId,uuid,log_idoraudit_log_id, else the record's content; with the organisation and the token's name it makes the event id, so a retried delivery stores nothing twice. - Time from
timestamp,time,created_at,createdAt,event_time,eventTime,occurred_at,dateordate_create(RFC 3339, Unix seconds or milliseconds); without one, the arrival time. - Type from
event_type,eventType,type,action,event,nameoroperation(defaultevent). Actor fromactor_email,user_email,email, oremailorloginof anactororuserobject. - The token's name is the events' source. The record is stored as sent: the sender's own settings decide what content reaches Govern.
The answer is 200 {"accepted", "stored", "rejected", "reason"}.
Limits and answers
| OTLP | Webhook | |
|---|---|---|
| Content types | application/x-protobuf, application/json | application/json |
| Body | 8 MiB, 32 MiB after gzip | 4 MiB, 16 MiB after gzip |
| Records per request | 10,000 | 5,000 |
Content-Encodingother than gzip is415.- A record over 256 KiB, timed before 1970 or more than a day ahead is rejected on its own and counted (OTLP partial success; webhook
rejectedandreason): one bad record never fails a batch. 401for a missing, unknown or revoked token;403when the connector is off;503on a store failure (retry).- Each token is rate limited on its own: 20,000 requests a minute, burst 2,000.
- A push connector reports
pendinguntil its first event andhealthyafterwards. It cannot tell a quiet day from a broken sender.
Reading payloads of pushed events needs audit.vendor_content:view, as for pulled events. See Vendor AI audit connectors.