Reduce signup friction with continuous onboarding
Give visitors a useful result before account creation, then carry their reviewed work into the authenticated product and complete onboarding there.
Use signup continuity when a visitor starts useful work on a public website, creates an onboarding draft, then signs in or creates an account before completing the workflow. Noodle Seed keeps one conversation and one authoritative state record across that identity change.
The end-to-end path is:
public conversation → interactive draft → signup → authenticated continuation → confirmation → completed setup
Why this can improve signup and onboarding conversion
This pattern is designed to reduce signup friction by moving the account request later, after the visitor has received a useful result. The visitor can begin with a low-effort natural-language description, answer only questions the assistant cannot already resolve, and review a concrete draft before deciding whether to create an account. After signup, their reviewed work and visible conversation continue with them instead of starting over.
The goal is reciprocity and preserved effort, not entrapment. Keep signup optional until identity is actually required, state clearly what creating an account enables, and never manufacture progress or urgency. This design gives customers a credible conversion hypothesis; it does not guarantee a conversion lift. Measure the useful-result, signup, onboarding-completion, and first-value stages separately against the existing flow.
Start with a useful result
For a short SaaS onboarding flow, the visitor usually already knows the information you need. Ask only for missing details, show a useful editable result, and let them decide whether an account is worth creating. Keep ordinary signup available. Do not add research, document uploads, or a workflow engine unless a specific outcome requires them.
The Stateful Draft reference shows a three-field brief that can be reviewed and saved before signup. Its widget calls typed state tools and uses the returned revision; a chat message or a local widget update is not proof of persistence. The example's account continuation reads the adopted draft. It deliberately does not create a business record in another product.
| Step | Noodle Seed provides | Your application provides |
|---|---|---|
| Start without an account | Public assistant, explicit tool allowlist, budgets | Page placement and the first useful outcome |
| Review and save | Typed tools, confirmation, optional expiring caller state | Required fields and validation rules |
| Choose an account | Sign-in card and single-use continuation ticket | Existing login/signup and a bound login transaction |
| Continue inside the product | State adoption and conversation restoration | Verified identity and the same-origin session endpoint |
| Finish the real task | Confirmed invocation of the same typed tool | Authorized, idempotent create/update API and the authoritative record |
If a draft only needs to be visible during the conversation, start without a persisted handle. If it must survive the identity change as structured data, use the opt-in state below. State expiry is a limit, not a guarantee that an anonymous conversation will survive an arbitrary new visit or browser.
1. Offer sign-in from a mixed assistant surface
Declare a publicWebsite assistant surface with signIn: true. Identity-dependent capabilities remain
visible, but reaching one produces a sign-in card instead of executing it.
Use one assistant and two exact capability projections. Keep the final business write off the public
surface, so the identity-dependent continueDraft read triggers signup without accidentally completing
onboarding:
access: [
publicWebsite({
origins: ["https://www.example.com"],
capabilities: [openDraft, saveDraft, continueDraft],
signIn: true,
}),
authenticatedWebsite({
origins: ["https://app.example.com"],
capabilities: [openDraft, saveDraft, continueDraft, completeOnboarding],
}),
],The card copy comes from labels: signInHeading, optional signInBody, signInAction, and
signUpAction. When signUpAction is present, the assistant-sign-in-requested event includes
intent: "sign-in" | "sign-up" so the host can choose login or registration. Headless renderers see the
same moment as a data-sign-in transcript part from subscribeChat.
The event also contains a minutes-lived, single-use signInTicket. POST it to your same-origin backend
before redirect and bind it to the short-lived login or signup transaction. Never put the raw ticket in a
URL, analytics event, log, or durable browser storage. The ticket identifies a conversation; it is not an
authentication credential or proof of account ownership.
2. Opt in only the onboarding state that should move
Mark each caller-scoped handle that may follow the visitor into their account:
state: {
handles: {
onboardingDraft: {
kind: "draft",
version: "v1",
scope: "caller",
ttlSeconds: 24 * 60 * 60,
claimOnAuthentication: true,
schema: z.object({
businessName: z.string().optional(),
setupStage: z.enum(["discover", "review", "ready"]),
}),
},
},
},claimOnAuthentication: true requires explicit scope: "caller" and a finite ttlSeconds. Handles
without the flag do not move. This keeps unrelated or privacy-sensitive visitor state outside the signup
transition.
3. Spend the ticket after authentication
After your identity system verifies the user, spend the ticket from your backend with the ordinary session helper:
const session = await createAssistantSession({
serviceUrl,
clientId,
clientSecret,
// Choose the allowlisted origin where the authenticated panel will run.
origin: process.env.PUBLIC_APP_ORIGIN!,
user: { id: user.id, email: user.email, roles: user.roles },
signInTicket,
});The widget calls its session endpoint with credentials: "same-origin". A cross-origin session endpoint
does not receive your authentication cookies and returns 401. Use the full-page redirect, land on the origin
passed above, and let that page call its own same-origin session endpoint.
Hosted Noodle Seed commits these effects in one PostgreSQL transaction:
- Validate the unexpired, unspent ticket, issuing tenant, anonymous session, and authenticated caller.
- Move existing records for opted-in handles to that caller while preserving schema version, value, revision, status, timestamps, and TTL.
- Replace the anonymous session token and bind the session to the authenticated caller and destination origin.
- Spend the ticket and write
assistant.state.claimedaudit evidence.
If signup is abandoned or the ticket expires, no ownership changes. The anonymous draft and conversation expire under their existing limits.
4. Handle refusals deliberately
A refused spend throws AssistantSessionExchangeError. Branch on error.elevationRefusal:
elevation_ticket_expired: invite the visitor to retry the protected action, which produces a fresh ticket.elevation_tenant_mismatch: alert and do not retry. The backend credentials do not own that conversation.elevation_state_conflict: the authenticated account already owns the same state key. Noodle Seed does not merge generic drafts. The valid ticket is consumed, state and session remain unchanged, and the host should open or restart the signed-in flow intentionally.
Replay, wrong-account reuse, already-elevated sessions, and expired sessions fail closed. The old anonymous token cannot act after a successful elevation.
5. Restore the visible conversation
On authenticated reattach, the client replays the service's bounded standard assistant event stream in this order:
- Visible user and assistant text retained by the short-lived session.
- A "Continued securely with your account" status.
- The latest MCP App view, re-resolved from the current surface artifact.
- At most one still-live confirmation or input request.
- Follow-up suggestions.
Restoring a pending interaction suppresses automatic resume, so the visitor never sees two live ways to complete the same action. Noodle Seed persists a bounded view descriptor, not App HTML or DOM. It does not promise exact focus or scroll coordinates across a full navigation.
For a privacy-sensitive application, pass restoreConversation: false to createAssistantSession. This
suppresses browser replay while keeping the normal bounded server and model continuity. Older services that
do not advertise endpoints.transcript start the panel visually fresh while retaining server-side context.
6. Complete onboarding as a separate product action
Signing up is not completed onboarding. After the authenticated assistant restores the draft and resumes the intercepted read, let the user review any remaining information and explicitly choose the final action. Keep that action on the authenticated surface and route it to the application's authoritative API:
const completeOnboarding = tool("complete_onboarding", {
title: "Create my workspace",
description: "Complete onboarding with the reviewed setup.",
input: z.object({}),
output: z.object({ workspaceId: z.string(), completed: z.boolean() }),
annotations: annotations.action({ confirm: true }),
fulfil: ({ connectors, user }) =>
connectors.product.completeOnboarding({
accountId: user.id,
idempotencyKey: user.id,
}),
});The connector is deliberately application-specific. It should call the existing authorized create or update endpoint, enforce tenant membership and idempotency, and return the durable record identifier. Report success only after that API confirms it. Neither account creation nor state adoption is permission to run this business write.
7. Measure the complete funnel
Instrument the customer's application at durable boundaries rather than treating conversation volume as conversion. A minimal funnel is:
useful_result_shown: the visitor received the actual draft or recommendation.draft_saved: the reviewed state write succeeded.signup_started: the customer application accepted the bound sign-in request.signup_completed: the identity provider and account creation succeeded.onboarding_completed: the authoritative product API completed setup.first_value_completed: the new account achieved the product-specific useful outcome.
Do not include prompts, draft fields, email addresses, tickets, tokens, or business-record contents in these events. Compare completion rates and time-to-first-value with the prior flow. Treat an improvement as observed customer evidence only after that measurement, not as a platform guarantee.
Production checklist
- The visitor receives a useful result before asking for an account.
- The signup invitation explains the real benefit of continuing and does not fabricate progress or urgency.
- The public surface uses the smallest useful capability allowlist and explicit daily budgets.
- The ticket travels only through a same-origin POST and a short-lived authentication transaction.
- Only finite-TTL caller handles that should move opt into
claimOnAuthentication. - The backend spends the ticket only after verifying the final user.
- The destination origin is allowlisted and matches the page hosting the authenticated assistant.
- Conflict handling never merges generic state automatically.
- The final business action is authenticated, confirmed, authorized, and idempotent.
- Signup completion and onboarding completion are measured as different events without customer content.
- Privacy-sensitive deployments set
restoreConversation: falseand use honest continuation copy.
Embed an assistant in your SaaS
Deploy a Noodle Seed assistant, exchange your existing signed-in user on the backend, and mount the published browser SDK without exposing credentials.
Embed an assistant with Django and Vue
Generate a tested Django session boundary and static Vue mount, preserving your existing login, CSRF protection, and package managers.