# Signed event sources > Publish a public signing key and exact agent run configuration, then review activation separately. Section: Synapse and Locus. Web page: https://docs.katara.com/products/event-sources/ An event source binds a public signing key, input rules and an exact agent run request. Publication records an immutable version. It does not activate the source or start a run. Read `execution_ready` before enabling: false means the event admission worker is unavailable. A true value does not prove that an event has produced a run. Open **Synapse → Event Sources** to inspect the bounded directory, publish a new source or revision, and review enable, pause or retirement separately. The page uses the same exact agent, input, grants, budget and output selection as an ordinary run. Preparing that selection does not submit a run. ## Publish public metadata Create and retain the Ed25519 signing private key in the external source's secret store. Send only its 32-byte public key, encoded as canonical padded base64, with an explicit signing key UUID. The portal and SDK do not generate or persist the private key. Publishing a new key requires a new immutable source revision and separate activation. Choose an input mode: - `fixed` uses the exact prepared run input reference. Event input overrides are refused. Each run reads that input as the person who enabled the source, while they can still read it, so the agent needs no share or grant for it. - `event_reference` requires each signed event to identify current approved JSON content through an exact reference. Raw event bodies do not become run input. The prepared run's input remains an explicit publication validation pin. The agent reads an event's input with its own access, so share each such document with it and name it in a content grant. Set `max_event_age_seconds` from 10 through 86,400 and `max_pending_events` from 1 through 10,000. These bounds do not extend content, grants, consent or budget lifetimes. Each event must still pass current authority and funding checks. An event's run pays the way its budget pays: credits, or the wallet a budget is [paid from](https://docs.katara.com/products/wallet-budgets.txt), checked for each event as for a run started by hand. An event source cannot pay its runs with x402, and cannot use a budget [paid per run](https://docs.katara.com/developers/x402.txt#pay-for-an-agent-run). Read the current environment and include `expected_environment_epoch` and `expected_reset_generation`. Choose a source UUID and `expected_revision: 0` for its first publication. Later publications use the current head revision. An active version can differ from that head. ```typescript const scope = { org_id, environment_id }; const operation = await client.eventSources.publish(scope, { source_id, expected_revision: 0, expected_environment_epoch: environment.authority_epoch, expected_reset_generation: environment.reset_generation, configuration: { name: "Reviewed source events", signing_key: { id: signing_key_id, public_key }, input_mode: "fixed", max_event_age_seconds: 300, max_pending_events: 100, run, }, }, publicationKey); const record = await client.eventSources.getVersion(scope, operation.resource_id); ``` Python exposes `client.event_sources` with `publish`, `get`, `get_version`, `list`, `set_enabled` and `retire`. TypeScript uses `getVersion` and `setEnabled`. Publication and reads accept current human sessions or appropriately scoped keys with `synapse:event_sources:write`. Workload credentials cannot author sources. ## Review activation separately Enable, pause and retirement require a human session. Enabling also requires an available event admission worker. Submit the exact version reference, observed `expected_generation`, both environment preconditions, and an explicit `enabled` boolean. Use generation zero if no activation exists. When pausing, inspect `activation.version`: it may be older than the publication head. Retirement uses the same exact version and generation preconditions without `enabled`. It is terminal for that source identity. Publication does not revive a retired source. Test simulation, paid Test Compute and Live retain their separate current permissions and funding requirements. An event signature does not grant additional run authority or transfer consent across environments. ## Inspect and recover `list` accepts `limit` from 1–50, `after` and `include_retired`; its default limit is 25. Continue explicitly with `next_cursor`. Use `get` for the current head and `getVersion` for a specific immutable version. Keep the exact request and caller-selected idempotency key after an unknown mutation outcome. The portal retains only the operation kind, identifiers, versions, environment pins, key and request digest in account-scoped tab storage. Reconstruct the exact reviewed configuration after reload. Changed configuration cannot replace the original request or key. Recheck current access and retry only through explicit review and acknowledgment; the SDK does not retry automatically. An operation receipt confirms its historical mutation, not current execution or authority. Reset and revocation may refuse recovery while the original uncertain metadata remains retained. No simulated event preview is provided. ## Inspect signed-event receipts Use `client.eventSources.listReceipts(scope, source_id, { limit: 25 })` and continue with `next_after` supplied as `after`. `getReceipt(scope, receipt_id)` reads an exact receipt. Python provides `list_receipts` and `get_receipt`. These reads use current `synapse:event_sources:write` authority and any exact source-resource restriction on the credential. The portal reads receipts only after **Inspect Signed Event Receipts** or an explicit exact receipt lookup. It shows the event ID, immutable source version, activation generation, received and expiry times, and recorded state. `pending` means admission remains pending; `denied` and `expired` do not authorize a run. Only `admitted` includes a canonical run ID, which links to run inspection. It does not mean executing or completed. A receipt can refer to an older activation generation while remaining visible within the current environment reset. Private actor, run template, signature time and worker lease fields are excluded. Input content is never fetched by receipt inspection. ## Sign at the external source The producer sends a raw JSON envelope, at most 16,384 bytes, to `POST /katara/v1/orgs/{org_id}/environments/{environment_id}/event-sources/{source_id}/events`. Use `Content-Type: application/json` and `X-Katara-Event-Signature`. Do not attach API credentials, session authorization, cookies, browser Origin, Idempotency-Key or Content-Encoding. Query parameters are refused. The public key comes from the exact activated source version; signing does not replace its current authority. The flat envelope contains `org_id`, `environment_id`, `schema_version: 1`, `source_id`, exact `source_version`, `activation_generation`, `mode`, `reset_generation`, `event_id`, `occurred_at`, optional `input_ref`, and `signed_at`. Both timestamps must be UTC whole seconds ending in `Z`. `occurred_at` cannot follow `signed_at`. The signature time must be newer than five minutes ago and no more than thirty seconds in the future. The source's event-age bound applies to new admission. Sign these bytes with Ed25519, without hashing them again through a separate prehash signature mode: ```text UTF8("katara.synapse.event.v1") || 0x00 || UTF8(signing_key_id) || 0x00 || UTF8("sha256:") || ASCII(lowercase_hex(SHA256(exact_body_bytes))) ``` Encode the 64-byte signature as canonical padded standard base64 in the signature header. Preserve the exact signed body bytes during transmission; reserializing JSON can change its hash. Duplicate, unknown or incorrectly cased fields are refused. A persisted receipt returns HTTP 202. Invalid signature and unavailable source authority are not distinguished by the HTTP 403 response. A fresh signature can repeat the same semantic event ID and metadata after an uncertain response, subject to current source authority. Changing the source version, activation, reset, mode, input reference or other semantic event fields cannot reuse that receipt identity. `signed_at` is excluded from the semantic event digest, allowing a fresh signature over the same event. Source changes can refuse renewed ingress; authorized receipt inspection remains the separate metadata recovery path. The portal never sends producer events or handles the signing private key.