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:
- authenticates the HMAC over the timestamp and the raw body, in constant time;
- rejects duplicate security headers, which is how a proxy-confusion attack starts;
- rejects stale timestamps, which is what stops a replay;
- bounds the body to 1 MiB by default;
- only then parses JSON.
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.
| Use | For |
|---|---|
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.