Silicon IAMDocs

Webhooks

Silicon IAM sends applications only the event categories and data covered by their current approved scopes, user consent, and selected memberships. The SDK verifies the signature over the exact bytes and hands you a parsed event; deduplication is yours, because only your database can do it durably.

Interpret application payloads by scope

The event model also represents Silicon subscription events; being able to parse an event does not mean an application is entitled to receive it. Applications receive immutable recipient-specific projections captured when the change commits. Invitation, governance, and tag-definition events require organization.invitations.read, organization.governance.read, and organization.tags.read, respectively.

self.profile.read discloses only display name, photo, description, and timezone. self.organizations.read discloses organization identifiers, name, logo, description, and version. self.trust.read exposes effective trust from the user’s perspective, not rules, defaults, or overrides. Raw trust configuration requires organization.trust.read.

Application directory/governance scopes do not expose raw SSO events or Silicon credential-management and webhook/subscription configuration. A completed credential rotation can still notify an application of permitted authorization-epoch/access changes without exposing credentials. Silicon subscriptions use their separate event vocabulary and routing rules. Consult the webhook contract before assuming an event or field is available, and never replace undisclosed fields from a broader cached token.

Verifying a delivery

use silicon_iam_client::{WebhookSecret, WebhookSecretKeyring, WebhookVerifier};

let keyring = WebhookSecretKeyring::new(7, WebhookSecret::new(secret)?)?;
let verified = WebhookVerifier::new(keyring).verify(&headers, &body)?;

// In ONE database transaction: insert this ID into a unique
// deduplication table, and apply the event's local side effect.
let event_id = verified.event_id();
let event = verified.event();

What the verifier does before you see anything:

X-Silicon-IAM-Signature must have the exact canonical form v1=<64 lowercase hexadecimal characters>. It is HMAC-SHA256 over {timestamp}.{exact body bytes}; the version prefix is part of the header format, not part of the digest.

Approve a pending destination

Use applications().approve_webhook(app_id, version, &mutation). It activates an under-review or verified application's pending first or replacement endpoint without changing Application status or scopes. The current owning organization's Carbon owner/admin or an IAM platform administrator with applications.review may call it. The creator field is audit metadata, not separate authority; legacy Applications still under platform review cannot use this route to become verified.

let app_id = "acme>checkout";
let current = client.applications().webhook(app_id).await?;
// Obtain verified-channel step-up for application.webhook.approve
// on current.application_id, the internal UUID included in fresh reads.
let mutation = Mutation::new().step_up(step_up);
let webhook = client.applications()
    .approve_webhook(app_id, current.version, &mutation)
    .await?;

The SDK sends the current Application version, idempotency key and assertion, with an empty JSON object and no request fields. The result is ApplicationWebhook with its incremented Application version, not a secret response. No pending endpoint or a non-verified Application is a conflict; a stale version is a precondition failure. Test endpoints normally activate immediately and need no approval. The matching backend must be deployed before using this method.

Secret versions

The Application supplies its initial secret during registration. Explicit rotate_webhook_secret accepts a caller-chosen successor and immediately uses it for new deliveries. Rotation requires the current Application version and a verified-channel step-up assertion for application.webhook_secret.rotate.

Replacing a production webhook URL rebinds its existing secret under an incremented version unless the request supplies a successor. In a testing environment, a URL replacement installs the supplied test-only webhook_secret, or generates one when omitted. The response returns webhook_signing_secret and secret_replay_expires_at whenever it installs a new secret.

Keep every version that can still have an in-flight delivery:

let mut keyring = WebhookSecretKeyring::new(7, WebhookSecret::new(secret)?)?;

// A URL replacement created version 8 while v7 deliveries can still retry.
keyring.rebind_version(7, 8)?;

An explicit secret rotation uses the different key material you supplied. Install that with keyring.insert(new_version, WebhookSecret::new(new_secret)?) and retain the prior entry until its complete retry window has elapsed.

Deliveries carry X-Silicon-IAM-Key-Version, so the verifier selects by version rather than trying each secret in turn. Without the rebind, in-flight v7 retries fail verification and dead-letter — a self-inflicted outage that looks like an IAM problem.

Deduplicate, and order by version

Delivery is at least once. A duplicate is normal, not exceptional, and arrival order is not guaranteed.

Testing-environment deliveries

A test delivery is not shaped like a production event. Its exact signed body has one top-level test object containing testing_key, metadata, and data. Verify the signature over those outer bytes first; then deduplicate on test.metadata.event_id and order on test.metadata.aggregate.version.

let verified = verifier.verify(&headers, &body)?;
if verified.is_testing() {
    // Constant-time comparison. The received key is never exposed by the SDK.
    verified.verify_testing_environment(&expected_environment_key)?;
}

// event() is normalized to WebhookEvent for both wire shapes.
let event = verified.event();

The complete setup, import, login, negative-boundary assertions, and cleanup sequence is in Testing environments.

UseFor
verified.event_id() Deduplication. Insert into a unique index in the same transaction as the side effect.
The aggregate version on the event Ordering. It is the only reliable sequencing signal.

Respond 2xx quickly and do the work asynchronously. A slow endpoint becomes a retrying endpoint, and a retrying endpoint becomes a dead-lettered one.

Event types

The authenticated event name is available as verified.event().event_type. Treat it as a versioned public vocabulary and keep an unknown-type branch: adding a new event must not make your receiver reject an otherwise valid delivery or skip the quick 2xx response.

Recovering missed deliveries

Anything that exhausts its retry cycle is dead-lettered and stays readable and replayable through the API. Replays preserve the original event ID, so your existing deduplication table handles them without any special case — see the contract's webhook section for the replay semantics.