---
title: "Push receivers"
---

# Push receivers

Receive OpenTelemetry events from Claude Code, Gemini CLI and OpenAI Codex, and JSON audit records from any service, with revocable ingest tokens and content withheld unless you choose to store it.

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

```http
POST /api/v1/vendor-audit/connectors/claude-code/tokens
{"name": "engineering laptops"}
```

- Needs `vendoraudit.connectors:manage`. The answer is `201` with the token (`kvat_...`), shown once, and `setup`: 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 .../tokens` lists 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

1. Create a `claude-code` token. 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/logs` and the token in `OTEL_EXPORTER_OTLP_LOGS_HEADERS`. Only the logs signal is configured.
2. Deploy them as Claude Code managed settings, which developers and repositories cannot override.
3. 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

```http
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`, `logs` or `entries` member is an array: up to 5,000 records, 4 MiB (16 MiB after gzip).
- Identity from `id`, `event_id`, `eventId`, `uuid`, `log_id` or `audit_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`, `date` or `date_create` (RFC 3339, Unix seconds or milliseconds); without one, the arrival time.
- Type from `event_type`, `eventType`, `type`, `action`, `event`, `name` or `operation` (default `event`). Actor from `actor_email`, `user_email`, `email`, or `email` or `login` of an `actor` or `user` object.
- 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-Encoding` other than gzip is `415`.
- 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 `rejected` and `reason`): one bad record never fails a batch.
- `401` for a missing, unknown or revoked token; `403` when the connector is off; `503` on a store failure (retry).
- Each token is rate limited on its own: 20,000 requests a minute, burst 2,000.
- A push connector reports `pending` until its first event and `healthy` afterwards. 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](https://neuralinverse.com/docs/evidence/vendor-connectors/).
