huudis.user.created.v1

Fires when a new Huudis user is provisioned — either by signup (someone created a Huudis account from any Forjio product's sign-in screen), by direct add from POST /v1/iam/users, or by an invite acceptance.

Subscribe if you back your own CRM, internal directory, or onboarding workflow with Huudis users.

Not yet emitted in v1. The event type is reserved in the catalog but the outbox publisher isn't wired for it currently. Subscribe defensively if you'd like to handle it when it ships — meanwhile, the audit log carries auth.signup and account.member_added entries with the same data.

When it fires

In every code path that creates a User row:

  • Self-service signup at /v1/auth/signup (someone clicks "Sign up" on a Forjio product).
  • Admin direct add at /v1/iam/users with an email that didn't previously have a Huudis user.
  • Social sign-in at /v1/auth/google/callback (or apple/facebook) with an email Huudis hasn't seen.
  • Invite accept at /v1/iam/invites/accept when the invitee doesn't have a pre-existing Huudis user.

If the email already had a Huudis user (because they're a member of another workspace), huudis.user.created.v1 does not fire — only huudis.account.member_added.v1 does, because the user existed already.

Payload

{
  "id": "evt_01KPG30TXM4N5Q8R1S4T6V8X0Y",
  "type": "huudis.user.created.v1",
  "createdAt": "2026-05-12T10:42:00.123Z",
  "data": {
    "id": "usr_01KPG40HMM…",
    "email": "newbie@forjio.com",
    "name": "Newbie",
    "emailVerified": false,
    "createdAt": "2026-05-12T10:42:00.123Z",
    "signupSource": "plugipay-portal"
  }
}

signupSource is the client_id of the OIDC client (or huudis-dashboard for direct signups). Useful for attributing acquisitions to specific products.

Handler examples

// Node
import { verifyWebhook } from '@forjio/huudis-node/webhooks';

app.post('/webhooks/huudis', async (req, res) => {
  const event = verifyWebhook(
    req.rawBody,
    req.headers['huudis-signature'],
    process.env.HUUDIS_WEBHOOK_SECRET
  );
  if (event.type === 'huudis.user.created.v1') {
    const u = event.data;
    await crm.contacts.upsert({
      huudisId: u.id,
      email: u.email,
      name: u.name,
      acquiredVia: u.signupSource,
    });
  }
  res.status(200).end();
});
# Python
from huudis.webhooks import verify_webhook

@app.post("/webhooks/huudis")
def handle(req):
    event = verify_webhook(req.raw_body, req.headers["huudis-signature"], WEBHOOK_SECRET)
    if event["type"] == "huudis.user.created.v1":
        u = event["data"]
        crm.upsert(huudis_id=u["id"], email=u["email"], source=u.get("signupSource"))
    return ("", 200)
// Go
event, err := huudis.VerifyWebhook(body, r.Header.Get("Huudis-Signature"), secret)
if err != nil { http.Error(w, "bad sig", 401); return }
if event.Type == "huudis.user.created.v1" {
    var u huudis.User
    _ = json.Unmarshal(event.Data, &u)
    crm.Upsert(ctx, u.ID, u.Email, u.SignupSource)
}

What to do

  • Upsert the user in your own directory keyed by id (stable Huudis user ID).
  • Kick off welcome emails or in-app onboarding tours.
  • Tag analytics with signupSource to attribute the acquisition.

Avoid:

  • Granting workspace permissions here. Workspace membership is a separate event — huudis.account.member_added.v1.
  • Sending the welcome email from your side if Huudis is already configured to. Huudis sends an "Email verification" email automatically; double-sending is annoying.

Common pitfalls

  • Treating email as unique forever. Users can change their email at /v1/account/email-change. Use id, not email, as your join key.
  • Assuming emailVerified: true. A fresh user has false until they click the verification link (or an admin force-verifies). Don't auto-trust the email until verification.
  • Double-processing across products. If two of your services both subscribe and both upsert into the same CRM, you'll race on the upsert. Pick one owning service.

Next