Noodle Seed
Guides

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.

Loading diagram…

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.

StepNoodle Seed providesYour application provides
Start without an accountPublic assistant, explicit tool allowlist, budgetsPage placement and the first useful outcome
Review and saveTyped tools, confirmation, optional expiring caller stateRequired fields and validation rules
Choose an accountSign-in card and single-use continuation ticketExisting login/signup and a bound login transaction
Continue inside the productState adoption and conversation restorationVerified identity and the same-origin session endpoint
Finish the real taskConfirmed invocation of the same typed toolAuthorized, 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.claimed audit 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:

  1. Visible user and assistant text retained by the short-lived session.
  2. A "Continued securely with your account" status.
  3. The latest MCP App view, re-resolved from the current surface artifact.
  4. At most one still-live confirmation or input request.
  5. 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:

  1. useful_result_shown: the visitor received the actual draft or recommendation.
  2. draft_saved: the reviewed state write succeeded.
  3. signup_started: the customer application accepted the bound sign-in request.
  4. signup_completed: the identity provider and account creation succeeded.
  5. onboarding_completed: the authoritative product API completed setup.
  6. 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: false and use honest continuation copy.

On this page