huudis.oidc.consent_granted.v1

Fires the first time a user consents to an OIDC client — either by clicking "Allow" on a third-party client's consent screen, or implicitly on first sign-in to a first-party client.

Subscribe if you want a clean "this user has now agreed to sign into my app" trigger — useful for kicking off in-product onboarding from the consumer side rather than the workspace-creation side.

Not yet emitted in v1. Audit log carries oidc_consent_granted.

When it fires

When a new OidcConsent row is created:

  • A third-party OIDC client's authorize flow reaches the consent screen, the user clicks "Allow", and they're redirected back with a code.
  • A first-party client's authorize flow completes for the first time (no visible consent screen for first-party).

Subsequent sign-ins to the same client do not re-fire the event — consent persists until the user revokes. If the user revokes and signs in again, a fresh consent row is created and the event fires again.

Payload

{
  "id": "evt_01KPG…",
  "type": "huudis.oidc.consent_granted.v1",
  "createdAt": "2026-05-12T11:02:00.000Z",
  "data": {
    "consentId": "ocs_01KPG…",
    "userId": "usr_01KPG…",
    "userEmail": "ibu.rina@cafe-sumur.id",
    "clientId": "oc_meja_studio",
    "clientName": "MejaStudio",
    "clientAccountId": "acc_01KPG30…",
    "scopes": ["openid", "profile", "email"],
    "isFirstParty": false,
    "consentedAt": "2026-05-12T11:02:00.000Z"
  }
}

clientAccountId is the workspace that owns the OIDC client (the one whose admin registered it) — not the user's own workspace. Useful for routing the event to that workspace's downstream systems.

Handler examples

// Node — your product is MejaStudio; this is the first-touch
if (event.type === 'huudis.oidc.consent_granted.v1' && event.data.clientId === MY_CLIENT_ID) {
  const c = event.data;
  await db.users.upsert({
    huudisId: c.userId,
    email: c.userEmail,
    consentedAt: c.consentedAt,
  });
  await onboarding.start(c.userId);
}
# Python
if event["type"] == "huudis.oidc.consent_granted.v1":
    c = event["data"]
    if c["clientId"] == MY_CLIENT_ID:
        db.users.upsert(huudis_id=c["userId"], email=c["userEmail"])
        onboarding.start(c["userId"])

What to do

  • Upsert the user in your own user table keyed by userId. This is the cleanest "new user of my product" trigger.
  • Trigger product-side onboarding (welcome email, tutorial unlock).
  • Tag your analytics with clientAccountId if you want to attribute users to the workspace that owns the client they signed into.

Avoid:

  • Treating consent as authentication. The user has agreed to let your app see their identity, but the session is independent; check the access token, not the consent.
  • Granting workspace permissions. Consent is per-client, not per-workspace. The user is using your client, not joining your workspace.

Common pitfalls

  • Consent vs. session. A user can consent and then never actually use the app. If you need "first active use" you want a product-specific event, not consent-granted.
  • Re-consent on scope expansion. If your client later adds a new scope to the request, the user is re-prompted on next sign-in — a fresh consent row is created and this event fires again with the new scope list.
  • First-party clients fire too. Filter by isFirstParty: false if you're only interested in third-party flows.

Next