
Why InvocaCare Doesn't Have a Sign-Up Button
A self-service Cognito sign-up flow was built, then deliberately reverted — because AWS's immutable-tenant-attribute guidance for multi-tenant Cognito pools makes it structurally impossible to assign a new user's tenant before their account exists.
Part 1 of 7 — InvocaCare Architecture series
Every SaaS product trains its users to expect the same first five minutes: land on a pricing page, click "Start free trial," type an email and a password, verify the email, you're in. It's such a deeply worn groove that when we started building InvocaCare — an answering-service platform for healthcare clinics — a self-service sign-up form wasn't a decision we made. It was just assumed. Of course there'd be a sign-up button.
There isn't one. Here's why, and what building it and then removing it taught me about where business assumptions and platform constraints actually meet.
The assumption
The pitch for self-service sign-up is obvious: a clinic manager finds InvocaCare, likes what they see, and starts a trial in the same sitting — no sales call, no waiting on us. For a small, resource-constrained founding team, that's not a nice-to-have, it's most of the growth model.
So we built it. A real Cognito SignUp / ConfirmSignUp flow — the native one, with Cognito's own confirmation-code verification, not a custom workaround. It worked. And then we reverted it, deliberately, before it shipped.
The constraint
InvocaCare is multi-tenant: every clinic is a tenant, and every user belongs to exactly one tenant. That tenant identity has to be attached to the user the moment their Cognito account exists — it's the thing every downstream authorization check, every DynamoDB query, every IAM policy depends on.
AWS's own guidance for this pattern is specific: the attribute that identifies a user's tenant should be immutable. That's not a style preference — an attribute you can change after the fact is an attribute an attacker (or a bug) can use to hop tenants. So custom:tenantId in our Cognito user pool schema is immutable, by design, matching AWS's documented best practice for custom-attribute-based multi-tenancy.
And immutable attributes in Cognito can only be set at one moment: user creation. Never after.
Here's where the self-service flow breaks. A genuinely open sign-up page — the kind where anyone can land and create an account — has no way to know in advance which tenant a new user belongs to. There's no tenant context yet; that's the whole point of it being self-service. So there is no mechanism — not a custom sign-up form, not a Pre-Signup Lambda trigger, not a Post-Confirmation trigger — that can correctly write custom:tenantId for a brand-new self-service user, immutable or not.