{"id":1581,"date":"2026-06-26T13:00:00","date_gmt":"2026-06-26T18:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1581"},"modified":"2026-03-07T16:25:59","modified_gmt":"2026-03-07T21:25:59","slug":"deep-linking-b2b-apps","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/deep-linking-b2b-apps\/","title":{"rendered":"Deep Linking for B2B and Enterprise Apps"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">B2B apps have deep linking requirements that consumer apps do not: multi-tenant workspaces, SSO authentication flows, IT admin controls, and enterprise security policies. A deep link to a project management task needs to route to the correct workspace, verify the user&#39;s permissions, and comply with the organization&#39;s security requirements before showing the content.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This guide covers deep linking patterns specific to B2B and enterprise apps. For enterprise onboarding, see <a href=\"https:\/\/tolinku.com\/blog\/onboarding-enterprise-apps\/\">onboarding for enterprise mobile apps<\/a>. For enterprise platform considerations, see <a href=\"https:\/\/tolinku.com\/blog\/enterprise-deep-linking-platforms\/\">enterprise deep linking platforms<\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"940\" height=\"627\" src=\"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/enterprise-team-tablet-1.jpeg\" alt=\"Diverse business team gathered around a tablet in a modern office, discussing enterprise mobile app strategy\" class=\"wp-image-2873\" srcset=\"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/enterprise-team-tablet-1.jpeg 940w, https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/enterprise-team-tablet-1-300x200.jpeg 300w, https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/enterprise-team-tablet-1-768x512.jpeg 768w\" sizes=\"auto, (max-width: 940px) 100vw, 940px\" \/><figcaption class=\"wp-element-caption\">Photo by www.kaboompics.com on Pexels<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Multi-Tenant Deep Links<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Workspace Routing<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">B2B apps serve multiple organizations. Every deep link needs a workspace context:<\/p>\n\n\n\n<pre><code>\/workspaces\/{workspace-slug}\/projects\/{project-id}\n\/workspaces\/{workspace-slug}\/tasks\/{task-id}\n\/workspaces\/{workspace-slug}\/documents\/{doc-id}\n\/{org-slug}\/settings\/billing\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The workspace slug ensures the user is routed to the correct tenant:<\/p>\n\n\n\n<pre><code class=\"language-swift\">func handleDeepLink(_ url: URL) {\n    let pathComponents = url.pathComponents\n\n    guard pathComponents.count &gt;= 3,\n          pathComponents[1] == &quot;workspaces&quot; || pathComponents[1] == &quot;w&quot; else {\n        showWorkspacePicker()\n        return\n    }\n\n    let workspaceSlug = pathComponents[2]\n\n    \/\/ Check if user has access to this workspace\n    WorkspaceManager.shared.switchTo(workspaceSlug) { result in\n        switch result {\n        case .success:\n            let contentPath = pathComponents.dropFirst(3).joined(separator: &quot;\/&quot;)\n            navigateToContent(contentPath)\n        case .noAccess:\n            showAccessDenied(workspaceSlug)\n        case .notMember:\n            showJoinWorkspacePrompt(workspaceSlug)\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Subdomain-Based Workspaces<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Many B2B apps use subdomains per organization:<\/p>\n\n\n\n<pre><code>https:\/\/acme.yourapp.com\/projects\/123\nhttps:\/\/contoso.yourapp.com\/tasks\/456\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Each subdomain needs its own Universal Links and App Links verification. To avoid managing hundreds of verification files, use a wildcard pattern:<\/p>\n\n\n\n<pre><code class=\"language-json\">{\n  &quot;applinks&quot;: {\n    &quot;apps&quot;: [],\n    &quot;details&quot;: [{\n      &quot;appIDs&quot;: [&quot;TEAM_ID.com.yourapp&quot;],\n      &quot;components&quot;: [{\n        &quot;\/&quot;: &quot;\/projects\/*&quot;\n      }, {\n        &quot;\/&quot;: &quot;\/tasks\/*&quot;\n      }]\n    }]\n  }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Serve the same AASA file from all subdomains via a wildcard DNS and web server configuration.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">SSO Integration<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">SAML\/OIDC Redirect Flow<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Enterprise users authenticate via their identity provider (Okta, Azure AD, Google Workspace). Deep links must handle the SSO redirect:<\/p>\n\n\n\n<pre><code>User taps deep link \u2192 https:\/\/acme.yourapp.com\/tasks\/456\n  \u2192 App opens, user not authenticated\n    \u2192 Redirect to SSO: https:\/\/sso.acme.com\/saml?RelayState=tasks\/456\n      \u2192 User authenticates with corporate credentials\n        \u2192 SSO redirects back: https:\/\/acme.yourapp.com\/sso\/callback?token=xyz&amp;relay=tasks\/456\n          \u2192 App receives token, navigates to tasks\/456\n<\/code><\/pre>\n\n\n\n<pre><code class=\"language-kotlin\">class DeepLinkActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        val uri = intent.data ?: return\n        val targetPath = uri.path ?: return\n        val workspace = extractWorkspace(uri)\n\n        if (authManager.isAuthenticated(workspace)) {\n            navigateTo(targetPath)\n        } else {\n            \/\/ Store pending deep link, start SSO\n            pendingDeepLink = targetPath\n            val ssoUrl = authManager.getSSOUrl(workspace, relayState = targetPath)\n            startSSOFlow(ssoUrl)\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">MDM and Managed App Configuration<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Enterprise apps deployed through Mobile Device Management (MDM) can receive configuration that affects deep linking:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;!-- Managed App Configuration (iOS) --&gt;\n&lt;dict&gt;\n    &lt;key&gt;workspace_url&lt;\/key&gt;\n    &lt;string&gt;https:\/\/acme.yourapp.com&lt;\/string&gt;\n    &lt;key&gt;sso_provider&lt;\/key&gt;\n    &lt;string&gt;okta&lt;\/string&gt;\n    &lt;key&gt;allowed_deep_link_domains&lt;\/key&gt;\n    &lt;array&gt;\n        &lt;string&gt;acme.yourapp.com&lt;\/string&gt;\n        &lt;string&gt;links.yourapp.com&lt;\/string&gt;\n    &lt;\/array&gt;\n&lt;\/dict&gt;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Enterprise Security for Deep Links<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Link Validation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Enterprise apps should validate deep links more strictly than consumer apps:<\/p>\n\n\n\n<pre><code class=\"language-swift\">func validateDeepLink(_ url: URL) -&gt; Bool {\n    \/\/ 1. Check allowed domains\n    let allowedDomains = ManagedConfig.shared.allowedDomains\n    guard let host = url.host, allowedDomains.contains(host) else {\n        log.security(&quot;Blocked deep link from unauthorized domain: \\(url.host ?? &quot;nil&quot;)&quot;)\n        return false\n    }\n\n    \/\/ 2. Check for prohibited paths\n    let blockedPaths = [&quot;\/admin&quot;, &quot;\/settings\/delete&quot;, &quot;\/export&quot;]\n    if blockedPaths.contains(where: { url.path.hasPrefix($0) }) {\n        log.security(&quot;Blocked deep link to restricted path: \\(url.path)&quot;)\n        return false\n    }\n\n    \/\/ 3. Validate URL structure\n    guard url.scheme == &quot;https&quot; else { return false }\n\n    return true\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Audit Logging<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Enterprise customers require audit logs for compliance. Log all deep link accesses:<\/p>\n\n\n\n<pre><code class=\"language-javascript\">async function logDeepLinkAccess(userId, url, workspace, result) {\n  await auditLog.create({\n    eventType: &#39;deep_link_access&#39;,\n    userId,\n    workspaceId: workspace.id,\n    url: url.toString(),\n    path: url.pathname,\n    result, \/\/ &#39;success&#39;, &#39;access_denied&#39;, &#39;not_found&#39;, &#39;auth_required&#39;\n    timestamp: new Date(),\n    ipAddress: request.ip,\n    userAgent: request.headers[&#39;user-agent&#39;]\n  });\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Data Loss Prevention<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Some enterprises block external sharing. Deep links shared outside the organization should be restricted:<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">fun canShareDeepLink(workspace: Workspace, targetUser: User?): Boolean {\n    val sharingPolicy = workspace.securityPolicy.externalSharing\n\n    return when (sharingPolicy) {\n        SharingPolicy.ALLOWED -&gt; true\n        SharingPolicy.INTERNAL_ONLY -&gt; targetUser?.workspaceId == workspace.id\n        SharingPolicy.DISABLED -&gt; false\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Notification Deep Links for B2B<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Business-Critical Notifications<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">B2B notifications often carry more urgency than consumer apps:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Notification<\/th>\n<th>Deep Link<\/th>\n<th>Priority<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Assigned a task<\/td>\n<td><code>\/w\/{ws}\/tasks\/{id}<\/code><\/td>\n<td>Normal<\/td>\n<\/tr>\n<tr>\n<td>Approval requested<\/td>\n<td><code>\/w\/{ws}\/approvals\/{id}<\/code><\/td>\n<td>High<\/td>\n<\/tr>\n<tr>\n<td>Mentioned in comment<\/td>\n<td><code>\/w\/{ws}\/comments\/{id}<\/code><\/td>\n<td>Normal<\/td>\n<\/tr>\n<tr>\n<td>System alert<\/td>\n<td><code>\/w\/{ws}\/alerts\/{id}<\/code><\/td>\n<td>Critical<\/td>\n<\/tr>\n<tr>\n<td>Contract expiring<\/td>\n<td><code>\/w\/{ws}\/contracts\/{id}<\/code><\/td>\n<td>High<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Email Deep Links<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">B2B apps send many transactional emails. Each should contain a deep link:<\/p>\n\n\n\n<pre><code class=\"language-html\">&lt;p&gt;You&#39;ve been assigned a new task:&lt;\/p&gt;\n&lt;p&gt;&lt;strong&gt;Q3 Revenue Report&lt;\/strong&gt;&lt;\/p&gt;\n&lt;p&gt;Due: July 15, 2026&lt;\/p&gt;\n&lt;a href=&quot;https:\/\/acme.yourapp.com\/tasks\/TASK-789&quot;&gt;View Task&lt;\/a&gt;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Invitation and Onboarding Deep Links<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Team Invitation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When inviting a colleague to a workspace:<\/p>\n\n\n\n<pre><code>https:\/\/yourapp.com\/invite\/{invite-token}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The invite link should:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Work even if the invitee does not have the app or an account.<\/li>\n<li>Create the account, join the workspace, and land on the relevant content.<\/li>\n<li>Expire after a configurable period (IT admin setting).<\/li>\n<\/ol>\n\n\n\n<pre><code class=\"language-swift\">func handleInviteDeepLink(_ url: URL) {\n    guard let token = url.pathComponents.last else { return }\n\n    InviteService.shared.validateToken(token) { result in\n        switch result {\n        case .valid(let invite):\n            if AuthManager.shared.isAuthenticated {\n                joinWorkspace(invite.workspaceId)\n            } else {\n                showSignUpFlow(invite: invite)\n            }\n        case .expired:\n            showError(&quot;This invitation has expired. Ask your admin for a new link.&quot;)\n        case .revoked:\n            showError(&quot;This invitation has been revoked.&quot;)\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Tolinku for B2B Apps<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku<\/a> supports custom domain routing for B2B apps. Configure workspace-aware routes in the <a href=\"https:\/\/tolinku.com\/docs\/concepts\/deep-linking\/\">Tolinku dashboard<\/a>, and Tolinku handles the routing between app and web fallback. The web fallback can redirect to your SSO provider for authentication before showing content.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For enterprise onboarding patterns, see <a href=\"https:\/\/tolinku.com\/blog\/onboarding-enterprise-apps\/\">onboarding for enterprise mobile apps<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Implement deep linking for B2B applications. Handle SSO redirects, workspace routing, and enterprise security requirements for business apps.<\/p>\n","protected":false},"author":2,"featured_media":1580,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Deep Linking for B2B and Enterprise Apps","rank_math_description":"Implement deep linking for B2B applications. Handle SSO redirects, workspace routing, and enterprise security requirements.","rank_math_focus_keyword":"deep linking B2B apps","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-deep-linking-b2b-apps.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-deep-linking-b2b-apps.png","footnotes":""},"categories":[11],"tags":[147,20,238,69,146,93,239,450],"class_list":["post-1581","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-deep-linking","tag-b2b","tag-deep-linking","tag-enterprise","tag-mobile-development","tag-saas","tag-security","tag-sso","tag-workspace"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1581","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=1581"}],"version-history":[{"count":5,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1581\/revisions"}],"predecessor-version":[{"id":2878,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1581\/revisions\/2878"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1580"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1581"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1581"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1581"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}