Silicon Hook DocsOpen Hook ↗
Silicon Hook documentation

Rust client#

The package is silicon-hook-client (import silicon_hook_client). It is stateless: it stores no credentials, starts no listener or daemon, and never updates itself. The host keeps its tokens and decides when to call. The hook CLI is built on this crate only.

[dependencies]
silicon-hook-client = "1"

Three parts:

Module For
signin Getting a Silicon Accounts access token issued to Hook, as Hook's public client (no secret)
Client Hook API v3 with that token: a Silicon's hooks, history, access, delivery status
delivery Checking Ting callbacks and fetching the events they point to

Sign in#

Hook accepts Silicon Accounts access tokens whose audience is hook. Hook's own tools get them as a public client:

use silicon_hook_client::signin::SignIn;

# async fn demo() -> silicon_hook_client::Result<()> {
let sign_in = SignIn::production()?; // or SignIn::new("http://localhost:9590")? locally

// A Silicon: a short-lived token from `silicon-accounts login --app hook -q`.
let tokens = sign_in.exchange_slt("slt_...").await?;

// A Carbon: the device flow.
let device = sign_in.start_device(Some("my tool on build-box"), None).await?;
println!("open {} and enter {}", device.verification_uri, device.user_code);
let tokens = sign_in.wait_for_device(&device, |_progress| {}).await?;

let account = tokens.account.as_ref().expect("token responses carry the account");
println!("signed in as {} ({}), a {}", account.id, account.uuid, account.kind.as_str());
# Ok(()) }

wait_for_device honours the server's interval, adds five seconds on each slow_down, retries transient failures (reported through the callback) and gives up when the code expires (10 minutes). Tokens holds a 30-minute access_token, a rotating refresh_token, expires_in, refresh_expires_at, scope and the account (uuid, kind, id, display_name, pfp_url, and a Silicon's custodian).

Keep the pair together. Before the access token expires, call sign_in.refresh(refresh_token): it spends the refresh token you send and returns a new pair. Silicon Accounts ends the whole sign-in if a spent refresh token is presented again, so refresh one at a time per sign-in and store the new pair before using it. sign_in.revoke(refresh_token) signs out.

Failures are Error::SignIn with a kind you can act on:

SignInErrorKind Meaning
SltRefused(SltRefusal) The short-lived token was refused: AlreadyUsed, Expired, WrongApp, Unknown, NotAnSlt, Ended. Mint a fresh one.
SessionEnded The refresh token no longer works: sign in again.
Denied, Expired The device sign-in was denied, or its code expired.
NotEnabled Hook's sign-in setup at that Silicon Accounts does not allow this sign-in.
Unavailable { maybe_processed } Silicon Accounts could not be reached or failed. maybe_processed says whether the request may have been handled (a refresh token may then be spent).
Rejected Any other refusal; code, message and hint say what.

Servers that hold their own Silicon Accounts app secret use silicon-accounts-client's AppClient instead; any route works as long as the token's audience is hook.

Call Hook#

use silicon_hook_client::{Client, Mutation, models::{CreateHook, UpdateHook}};

# async fn demo(access_token: &str) -> silicon_hook_client::Result<()> {
let hook = Client::production()?.with_token(access_token);
let created = hook
    .create_hook("si:scout", &CreateHook { name: "GitHub".into(), ..Default::default() }, &Mutation::new())
    .await?;
println!("give GitHub {}", created.hook.endpoint_url);
hook.update_hook("si:scout", created.hook.id, &UpdateHook {
    description: Some(None), // explicit null clears it
    ..Default::default()
}, &Mutation::new()).await?;
# Ok(()) }

Client::new takes a pathless HTTPS origin, or plain HTTP on this machine (localhost, *.localhost, a loopback address). Redirects are off. Before the first call the client checks /api/version: the server must be silicon-hook serving API v3, and every request then pins Silicon-Hook-API-Version: v3.

with_token and with_telemetry return new, immutable clients. Every silicon argument accepts the Silicon's current si: id or its uuid; responses show accounts as {uuid, id} (and kind for created_by, grants and the like).

Area Methods
Sign-in and status sign_in_information (public), login_status, negotiate, version, health, contracts
Silicons and access silicons, access, grant, revoke, leave, allow_list, allow, disallow
Hooks list_hooks, get_hook, create_hook, update_hook, set_secret, delete_hook, restore_hook, set_enabled, rotate_endpoint, rotate_secret
Silicon Accounts updates connect_accounts_hook
History events, blocked_requests, event
Delivery through Ting delivery_status, register_recipient, receiving_subscription, subscribe, unsubscribe, publication, delivery_context, resolve_notification, hydrate_notification
Diagnostics emit_telemetry (opt-out with with_telemetry(false) or SILICON_HOOK_TELEMETRY=off)

Mutations take a Mutation: one idempotency key per logical change. Reuse the same Mutation when retrying after an uncertain result, and Hook answers with the first result instead of applying the change twice.

login_status returns authenticated: false without a token or when Hook refuses the token (401), with Hook's reason and message; outages stay errors.

Errors#

Error::Api keeps Hook's error envelope: status, the stable code, message, details, hint, request_id and retry_after. error.code(), error.status(), error.hint(), error.is_code("…") and error.is_unauthenticated() work across kinds. Other kinds: Invalid (nothing was sent), Transport (Hook unreachable), Protocol (not Hook API v3, or an unexpected answer), Json, and SignIn.

Secrets (Secret) never appear in Debug output and are wiped when dropped; expose() returns the plaintext where it must leave the program. Creation and rotation responses carry signing secrets once; store them privately and never log serialized responses.

Who can call what#

The Silicon and its custodian can do everything with the Silicon's hooks. manage grants create and change hooks; view grants read hooks, history and delivery status. Granting, revoking and the allow-list belong to the Silicon and its custodian. A custodian acts as itself, never as the Silicon. See Sign in to Hook.

Examples#

  • examples/management_login.rs: a Silicon signs in with a short-lived token, lists its hooks and signs out.
  • examples/ting_receiver_e2e.rs: a fixture host that receives Ting callbacks, hydrates them and accepts them durably.