Skip to content
Tolinku
Tolinku
Sign In Start Free
Use Cases · · 6 min read

Invite Link Onboarding: From Invitation to Active User

By Tolinku Staff
|
Tolinku e commerce deep linking dashboard screenshot for use cases blog posts

Invited users are different from organic users. They already have context: someone they know (or a team they belong to) sent them a link with a specific purpose. The onboarding flow should reflect that context instead of treating them like a stranger discovering your app for the first time.

For personalization strategies, see Personalized Onboarding Flows with Deep Link Data. For deferred deep linking mechanics, see Deferred Deep Linking: How It Works.

Different invitation types require different onboarding flows:

Invite Type Context Onboarding Goal
Team invite Workspace name, role, inviter Join the team, skip product education
Friend referral Referrer name, reward Create account, claim reward (see How Referral Deep Links Work)
Content share Shared item (photo, playlist, doc) View shared content, then sign up
Collaboration invite Project, task, or document Access the shared item, contribute
Event invite Event details, date, host RSVP, add to calendar

Implementation Pattern

async function createInviteLink(inviter, inviteType, metadata) {
  const link = await Tolinku.createLink({
    path: `/invite/${inviteType}`,
    params: {
      inviter_id: inviter.id,
      inviter_name: inviter.firstName,
      type: inviteType,
      ...metadata, // team_id, project_id, event_id, etc.
    },
    ogTitle: `${inviter.firstName} invited you to ${getInviteTitle(inviteType, metadata)}`,
    ogDescription: getInviteDescription(inviteType, metadata),
  });

  return link.url;
}

// Team invite
const teamLink = await createInviteLink(user, 'team', {
  team_id: team.id,
  team_name: team.name,
  role: 'member',
});

// Content share
const shareLink = await createInviteLink(user, 'share', {
  content_type: 'playlist',
  content_id: playlist.id,
  content_name: playlist.name,
});

Step 2: Capture Invite Context on First Launch

async function handleFirstLaunch() {
  const deferred = await Tolinku.checkDeferredLink();

  if (deferred && deferred.path.startsWith('/invite/')) {
    const params = deferred.params;

    inviteStore.set({
      type: params.type,
      inviterId: params.inviter_id,
      inviterName: params.inviter_name,
      teamId: params.team_id,
      contentId: params.content_id,
      timestamp: Date.now(),
    });

    return navigateToInviteOnboarding(params.type);
  }

  return navigateToDefaultOnboarding();
}

Step 3: Route to the Right Flow

function navigateToInviteOnboarding(inviteType) {
  switch (inviteType) {
    case 'team':
      return navigation.navigate('TeamInviteOnboarding');
    case 'share':
      return navigation.navigate('SharedContentView');
    case 'referral':
      return navigation.navigate('ReferralWelcome');
    case 'collab':
      return navigation.navigate('CollabOnboarding');
    default:
      return navigation.navigate('DefaultOnboarding');
  }
}

Team Invite Onboarding

Team invitations are the most common invite type for B2B and collaboration apps. The invitee already knows the product's purpose (their teammate uses it), so the onboarding should focus on getting them into the workspace quickly.

Flow Design

Standard onboarding (6 steps):

  1. Welcome screen
  2. Feature tour
  3. Account creation
  4. Profile setup
  5. Preferences
  6. Dashboard

Team invite onboarding (3 steps):

  1. "Alex invited you to join Design Team" (with team context)
  2. Account creation (email pre-filled if included in invite)
  3. Team workspace (already joined)

You can cut the flow in half because the invitation provides the context that the standard flow tries to establish.

Implementation

function TeamInviteOnboarding({ invite }) {
  const [step, setStep] = useState(0);

  const steps = [
    <TeamWelcome
      inviterName={invite.inviterName}
      teamName={invite.teamName}
      role={invite.role}
    />,
    <AccountCreation
      prefillEmail={invite.email}
      teamContext={invite.teamName}
    />,
    // Skip feature tour, preferences, etc.
    // User lands directly in the team workspace
  ];

  return (
    <OnboardingContainer
      steps={steps}
      currentStep={step}
      onNext={() => {
        if (step === steps.length - 1) {
          joinTeamAndNavigate(invite.teamId);
        } else {
          setStep(step + 1);
        }
      }}
    />
  );
}

async function joinTeamAndNavigate(teamId) {
  await api.joinTeam(teamId);
  navigation.reset({ routes: [{ name: 'TeamWorkspace', params: { teamId } }] });
}

Auto-Join vs. Approval

Decide whether invited users join automatically or need approval:

async function processTeamJoin(userId, invite) {
  const team = await getTeam(invite.teamId);

  if (team.autoApproveInvites) {
    // Direct join
    await addTeamMember(team.id, userId, invite.role);
    return { status: 'joined', teamId: team.id };
  }

  // Approval required
  await createJoinRequest(team.id, userId, invite.inviterId);
  return { status: 'pending_approval', teamId: team.id };
}

Content Share Onboarding

When someone shares content (a photo album, playlist, document, or product), the recipient's primary intent is to view that content. Account creation is secondary.

Show Content First, Ask for Account Later

function SharedContentOnboarding({ invite }) {
  const [hasViewed, setHasViewed] = useState(false);

  if (hasViewed === false) {
    return (
      <ContentPreview
        contentType={invite.contentType}
        contentId={invite.contentId}
        sharedBy={invite.inviterName}
        onView={() => setHasViewed(true)}
        onSignUp={() => navigation.navigate('QuickSignUp')}
      />
    );
  }

  return (
    <ContentEngagement
      prompt={`Want to save this ${invite.contentType}? Create a free account.`}
      onSignUp={() => navigation.navigate('QuickSignUp', { returnTo: invite.contentId })}
      onSkip={() => navigation.navigate('Browse')}
    />
  );
}

The key principle: let them see what was shared before asking for anything. A user who sees value in the content is far more likely to create an account than one who hits a signup wall immediately.

Gated vs. Ungated Content

Content Type Show Without Account? Why
Public post/photo Yes Low barrier, builds trust
Playlist/collection Preview (first 3 items) Tease value, encourage signup
Document/file Title + preview Security concern if fully open
Private group content No (require signup) Privacy of group members
Paid content Teaser only Business model requires account

Acknowledge the Inviter

Invited users arrive with a social connection. Use it throughout onboarding.

Welcome Screen Copy

Instead of a generic "Welcome to [App]," reference the invitation:

function getWelcomeMessage(invite) {
  switch (invite.type) {
    case 'team':
      return {
        headline: `${invite.inviterName} invited you to ${invite.teamName}`,
        subtext: `Join your team on [App] to collaborate in real time.`,
      };
    case 'share':
      return {
        headline: `${invite.inviterName} shared something with you`,
        subtext: `Open [App] to view it.`,
      };
    case 'referral':
      return {
        headline: `${invite.inviterName} thinks you'll love [App]`,
        subtext: `Sign up and you both get a reward.`,
      };
    default:
      return {
        headline: `You've been invited to [App]`,
        subtext: `Create your account to get started.`,
      };
  }
}

Social Proof Throughout

Show the inviter's presence during onboarding steps:

  • Account creation: "Join Alex and 12 others on Design Team"
  • Feature tour: "Alex uses [Feature] to manage design files"
  • Completion: "You're all set. Alex has been notified."

Small touches that reinforce the social connection reduce drop-off because the user feels expected, not anonymous.

Pre-Filling Invite Data

What to Pre-Fill

Data Source Pre-Fill Field User Action
Email invite Email address Confirm (locked)
Team invite Team name, role Auto-set
Calendar invite Event date/time Auto-add
Partner invite Organization Auto-associate

Implementation

function InviteAccountCreation({ invite }) {
  return (
    <Form onSubmit={handleSignUp}>
      {invite.email && (
        <Input
          label="Email"
          value={invite.email}
          disabled={true}
          helperText="Invitation was sent to this email"
        />
      )}

      <Input label="Password" type="password" />

      <Input
        label="Name"
        defaultValue={invite.recipientName || ''}
      />

      {invite.teamName && (
        <InfoBox>
          You'll be added to <strong>{invite.teamName}</strong> as a {invite.role}.
        </InfoBox>
      )}

      <Button type="submit">
        {invite.type === 'team' ? 'Join Team' : 'Create Account'}
      </Button>
    </Form>
  );
}

Invite Expiration and Validation

Expiration Rules

Invite links should expire. The appropriate duration depends on the invite type:

Invite Type Expiration Reason
Team invite 7-14 days Security; team membership should be timely
Content share 30 days Content may become irrelevant
Event invite Event date + 1 day No point after the event
Referral 30-90 days Marketing window

Validation Flow

async function validateInvite(inviteParams) {
  // Check if invite exists and hasn't been used
  const invite = await getInvite(inviteParams.inviter_id, inviteParams.type);

  if (invite === null) {
    return { valid: false, reason: 'not_found' };
  }

  if (invite.usedAt) {
    return { valid: false, reason: 'already_used' };
  }

  if (invite.expiresAt < Date.now()) {
    return { valid: false, reason: 'expired' };
  }

  // For team invites, check if team still exists
  if (inviteParams.type === 'team') {
    const team = await getTeam(inviteParams.team_id);
    if (team === null) {
      return { valid: false, reason: 'team_deleted' };
    }
  }

  return { valid: true, invite };
}

Handling Invalid Invites

When an invite link is expired or invalid, don't just show an error. Offer alternatives:

function InvalidInviteScreen({ reason, invite }) {
  const messages = {
    expired: {
      title: 'This invite has expired',
      action: `Ask ${invite.inviterName} to send a new invite.`,
    },
    already_used: {
      title: 'This invite has already been used',
      action: 'Log in to access your account.',
    },
    team_deleted: {
      title: 'This team no longer exists',
      action: 'Create your own account to get started.',
    },
    not_found: {
      title: 'Invite not found',
      action: 'The link may be incorrect. Ask the sender for a new one.',
    },
  };

  const msg = messages[reason];

  return (
    <Screen>
      <Heading>{msg.title}</Heading>
      <Text>{msg.action}</Text>
      <Button onPress={() => navigation.navigate('SignUp')}>
        Create Account
      </Button>
    </Screen>
  );
}

Measuring Invite Onboarding

Key Metrics

Metric What It Tells You
Invite click-to-install rate How compelling the invite preview is
Invite onboarding completion Whether the invited flow works
Time to first action How quickly invited users become active
Invite vs. organic retention (Day 7) Whether invited users stick around longer
Inviter notification open rate Whether the inviter re-engages when their friend joins

Tracking

analytics.track('invite_onboarding', {
  step: 'account_created',
  inviteType: invite.type,
  inviterId: invite.inviterId,
  stepsShown: 3, // vs. 6 for standard
  timeToComplete: Date.now() - invite.startedAt,
});

Compare invited user metrics against organic signups to validate that the abbreviated onboarding outperforms the standard flow for this segment.

For onboarding use cases, see the onboarding documentation. For referral features, see Tolinku referrals. For referral program setup, see the referral docs.

Get deep linking tips in your inbox

One email per week. No spam.

Ready to add deep linking to your app?

Set up Universal Links, App Links, deferred deep linking, and analytics in minutes. Free to start.