Hook DocsOpen Hook ↗
SILICON HOOK / DOCUMENTATION

Stateless Rust client#

The package is silicon-hook-client; the Rust import is silicon_hook_client. For this source workspace use a path dependency on crates/client. Consumers of a published release use its matching crates.io version. The SDK does not persist authentication, environment keys or recipient configuration. Your program owns those values. A login session refreshes tokens in memory and owns its relay tasks; your program controls its lifetime and shutdown.

Version 0.3.0 uses the new_event delivery envelope with data.sender and data.metadata. Upgrade the client/CLI and backend together; consumers of 0.2.x must update their event matching and field access to the new format.

Sign in#

Obtain an IAM short-lived token for tos>hook through IAM's existing Carbon/Silicon login. Do not ask the user for their password or OTP in Hook.

use silicon_hook_client::{Client, Mutation};

# async fn example() -> Result<(), Box<dyn std::error::Error>> {
let base = Client::new("https://backend.hook.teamofsilicons.com")?;
let iam = base.iam().await?;
println!("IAM application: {:?}", iam.app_id);
let slt = std::env::var("HOOK_SLT")?;
let login = Mutation::new();
let session = base.login_without_webhook(&slt, &login).await?;
session.webhook("http://127.0.0.1:9000/events")?;
let client = session.client();
let hooks = client.list_hooks("cos:tos", false).await?;
println!("{} hooks", hooks.items.len());
// Keep session alive while your application serves events.
session.shutdown().await?;
# Ok(()) }

Recipient validates HTTPS or local HTTP and rejects embedded credentials and fragments. Configure it after login with session.webhook(url), or pass it to Client::login for the combined flow. It is never serialized to the backend. Login starts the local request gateway, WebSocket relay and automatic token refresh. It reserves the local port before consuming the SLT. LoginOptions and login_with_options select Carbon streams, a different local port, or a relay notice channel. A Silicon defaults to its own stream; a Carbon with no selected streams still has a local request gateway. The default port is 18479; choose a different port if a CLI daemon already owns it. Nothing is persisted. Client::new accepts a pathless HTTPS origin or HTTP on loopback. Redirects are disabled so credentials cannot follow a redirect to another service.

Tokens have redacted Debug output and zeroize their owned strings when dropped. Use Secret::expose() only where plaintext is required. Serialization is explicitly allowed because applications need to save credentials securely. Avoid logging serialized token, hook-creation or environment-key responses.

The returned RelaySession automatically refreshes before token expiry; call session.client() again to get the latest immutable client. Its local_client() forwards identity-bound requests, local_token() supplies the local bearer, health() queries its gateway, and wait() reports terminal relay failures. shutdown() stops local work without revoking IAM; dropping it cancels its tasks. To revoke, use its current refresh token with logout, then shut down.

Hosts that already manage a persistent relay, such as the CLI, use authenticate(slt, mutation) and manage refresh themselves. The compatibility exchange_slt method accepts a recipient but also only sends the SLT. For this lower-level flow, refresh before expires_in elapses. Create one Mutation, pass it to refresh, and retain it for retries. Replace the complete old token pair atomically. To sign out the whole session family, construct a client with the refresh token as bearer and call logout(&mutation).

session.webhook(url) replaces the current destination; session.unhook() detaches delivery while retaining authentication, token refresh and the local gateway. Relay work is cancelled when the background task next runs; an in-flight request may already have reached the old recipient. Unacknowledged events replay when a destination is configured again. session.recipient() reads the local setting. All of this state stays in memory. LoginOptions::default() starts without a recipient; LoginOptions::new(recipient) starts with one.

client.iam() returns public configuration from the selected production/test backend before login. client.login_status() verifies the current bearer and organization membership online, returning an actor with authenticated: true. Absent credentials or a 401 produce authenticated: false; outages and other errors propagate. Select the organization with with_organization if the token exchange did not bind one.

Client selection and versioning#

with_token, with_organization, with_test_key and with_auto_update return clones with immutable configuration. They do not modify earlier clients. API requests first negotiate /api/version, verify service = silicon-hook and API v1, then pin requests with silicon-hook-api-version: v1.

A test client carries both a Hook test root key and a token minted in the linked IAM test world. See testing with Rust. Never attach a production token to a test client as a fallback after a failed test login.

Operations#

Area Methods
Authentication login, login_without_webhook, login_with_options, authenticate, exchange_slt, login_status, refresh, logout
Discovery iam, negotiate, version, health
Hooks list_hooks, get_hook, create_hook, update_hook, delete_hook, restore_hook
Activation set_enabled with one or more hook UUIDs
Credentials rotate_endpoint, rotate_secret
History events, blocked_requests
Delivery deliveries, acknowledge, delivery_cursor, stream
IAM connect_iam_hook
Test administration create_environment, list_environments, environment, environment_key, rotate_environment_key, delete_environment, restore_environment, list_environments_page
Test root operations current_environment, clean_environment, configure_test_iam
Local service RelaySession, Relay::run, local::serve_local, local::LocalClient
Updates updater::check, update_dependency, install_cli, find_manifest

All wire models are under models. CreateHook::default() plus a nonempty name enables default HMAC-SHA256 verification. A Signature override changes only fields supplied. UpdateHook.description and Signature.public_key distinguish omission (None) from explicit clearing (Some(None)).

# async fn example(client: silicon_hook_client::Client) -> silicon_hook_client::Result<()> {
use silicon_hook_client::{Mutation, models::{CreateHook, UpdateHook}};
let mutation = Mutation::new();
let created = client.create_hook("cos:tos", &CreateHook {
    name: "GitHub".into(), ..Default::default()
}, &mutation).await?;
client.update_hook("cos:tos", created.hook.id, &UpdateHook {
    description: Some(None), ..Default::default()
}, &Mutation::new()).await?;
# Ok(()) }

Failures, retries and limits#

Error::Api includes HTTP status, stable code, message, request ID and optional Retry-After. Transport errors mean the outcome of a mutation may be unknown. Reuse its Mutation for a retry; do not generate a new key. Server error bodies are decoded as structured Hook errors and are not dumped wholesale.

HTTP and WebSocket connection timeouts are 30 seconds. Stream writes time out after 5 seconds, and 125 seconds without any server traffic causes reconnect in the relay. WebSocket frames/messages are bounded at 4 MiB. Response buffering stops at 64 MiB; history is already paginated within a backend byte budget. A request may ask for 1–10000 history records, but a byte-limited page can contain fewer: continue with next_cursor. Delivery pulls allow up to 1000 events per call. Reading history or pulling deliveries does not ACK them.

Delivery is at least once. Only ACK after successfully processing all earlier retained events for that Silicon. ACK is cumulative per identity/Silicon, so multiple consumers using the same identity share a cursor. Use separate IAM identities when every consumer needs an independent copy.

Streams and relay#

stream(&[silicon_ids]) yields Ready, Ping, NewEvent, AckRecorded and Error frames. ServerFrame::NewEvent { data } carries the hook name in data.sender and the complete event in data.metadata. On the wire it has exactly type: new_event and data at the top level. Use the metadata's silicon_id and delivery_sequence when acknowledging. Stream::next immediately answers application and protocol pings. Keep calling it while doing recipient work. Stream::acknowledge and resume send the corresponding frames. Closing a stream never acknowledges pending events.

Use the relay module for ordered local HTTP delivery with reconnect and retry when your host manages its own lifecycle. Its credentials come through an in-memory watch channel. Your program can refresh and replace them without restarting the application.

The interactive crates/client/examples/relay_login.rs example exercises login and a live SDK relay. Run cargo run -p silicon-hook-client --example relay_login with its documented environment variables; enter one command at a time.

Updates#

Automatic client checks are on by default and at most hourly in one process, after an API request completes. The client checks crates.io and, when running inside a consuming Cargo project, can advance the dependency's lockfile within its manifest constraint. It skips Hook's own source workspace. Linked library code changes only after a new build/restart; an update cannot replace compiled code in a running process. Short-lived programs can use the explicit async updater functions and await them before exiting.

Disable with client.with_auto_update(false) or SILICON_HOOK_CLIENT_AUTO_UPDATE=false. Authentication remains stateless; update timestamps for the SDK are in memory. The CLI handles its own persistent hourly check and disables per-client background checks.

Bring your own secret (BYOS)#

Version 0.5.0 adds Client::set_secret. Creation and updates also accept Signature::secret to configure the verification policy and secret together.

# async fn example(client: &silicon_hook_client::Client) -> silicon_hook_client::Result<()> {
use silicon_hook_client::{Mutation, Secret, models::{CreateHook, Signature}};
let created = client.create_hook("cos:tos", &CreateHook {
    name: "Provider".into(),
    signature: Some(Signature {
        secret: Some(Secret::new("provider-secret")),
        secret_encoding: Some("utf8".into()),
        ..Signature::default()
    }),
    ..CreateHook::default()
}, &Mutation::default()).await?;
client.set_secret("cos:tos", created.hook.id,
    Secret::new("replacement-secret"), None, &Mutation::default()).await?;
# Ok(()) }

None retains the current secret encoding; pass Some("hex".into()) (or another supported encoding) to change it. The previous secret stops verifying immediately. Replacement preserves the URL, other policy fields and activation state and returns no secret. You may create with a generated secret first, then set your own after provider registration. A client selected with with_test_key applies the same operations in the isolated testing environment. Secret redacts Debug output; do not log serialized requests, which necessarily contain the secret.