Webhooks
Outbound webhook delivery for security events. Subscribers can be notified when users register, change passwords, get removed from orgs, etc.
The webhook system lives in identsphere-webhooks — a separate crate so hosts
that don't need outbound webhooks aren't forced to depend on it.
Signing
Every webhook delivery carries:
POST {your_url}
X-IdentSphere-Signature: t=1748448000,v1=abc...hmac_sha256_hex
X-IdentSphere-Event: user.registered
Content-Type: application/json
{
"event": "user.registered",
"data": { ... }
}
Verify the signature in your handler:
import crypto from 'crypto';
function verify(payload: string, header: string, secret: string): boolean {
const [tPart, sigPart] = header.split(',');
const t = tPart.split('=')[1];
const v1 = sigPart.split('=')[1];
// Reject anything older than 5 minutes — protects against replay.
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${payload}`)
.digest('hex');
// constant-time compare
return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
Same pattern as Stripe, GitHub, etc. — the timestamp prefix defeats replay.
Events
| Event | Payload |
|---|---|
user.registered | { user_id, organization_id, email } |
user.password_changed | { user_id } |
member.role_changed | { user_id, organization_id, old_role, new_role } |
member.removed | { user_id, organization_id } |
mfa.enabled / mfa.disabled | { user_id } |
Full list: see the Event enum in identsphere_webhooks::events.
Subscriptions
Endpoints are configured per-deployment, not per-user. Provide them at app startup:
use identsphere_webhooks::{WebhookDelivery, WebhookConfig};
let delivery = WebhookDelivery::new(WebhookConfig {
endpoints: vec![
("https://hooks.example.com/identsphere".into(), b"secret-here".to_vec()),
],
..Default::default()
});
Multiple endpoints fan out — each gets its own delivery + signature.
Retry policy
- Initial attempt + 4 retries with exponential backoff (1s, 4s, 16s, 60s, 300s).
- Successful responses are 2xx within 10s.
- 5xx or timeout → retry; 4xx → drop.
The retry queue is in-process. For production durability, wrap WebhookDelivery
in your own SQS / Kafka producer.
Local development
Use ngrok or webhook.site to receive deliveries from a localhost
IdentSphere instance. The signature scheme is identical to production.
Limits
- v0.1 webhooks are FIRE-AND-FORGET from the SDK's perspective — there's no "delivery succeeded" callback you can hook into.
- No event filtering per-endpoint in v0.1 (all subscribers get all events). Filter on the receiver side.
Cross-product wiring
The webhooks crate works fine without identsphere-axum, so non-Rust receivers just need to verify the HMAC. The signing key is whatever bytes you configured at the producer; rotate at will.