{"id":1404,"date":"2026-06-10T17:00:00","date_gmt":"2026-06-10T22:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1404"},"modified":"2026-03-07T03:49:11","modified_gmt":"2026-03-07T08:49:11","slug":"parallel-running-migration","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/parallel-running-migration\/","title":{"rendered":"Parallel Running: Operating Two Deep Link Platforms"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">The safest way to migrate between deep linking platforms is to run both simultaneously for a period. Parallel running lets you validate the new platform with real traffic while the old platform continues to serve as a safety net. If something goes wrong, you can route traffic back to the old platform immediately.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This approach adds complexity, but it dramatically reduces risk. This guide covers how to set up parallel operation, manage traffic routing, handle SDK coexistence, and plan the cutover. For the overall migration plan, see <a href=\"https:\/\/tolinku.com\/blog\/migration-timeline-planning\/\">migration timeline planning<\/a>. For analytics continuity during parallel running, see <a href=\"https:\/\/tolinku.com\/blog\/analytics-data-migration\/\">analytics data migration<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why Parallel Running<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A &quot;big bang&quot; migration (shut down old platform, turn on new platform) is faster but riskier. If anything goes wrong, all your deep links break at once.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Parallel running provides:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Validation with real traffic.<\/strong> You can compare the new platform&#39;s behavior against the old platform&#39;s behavior with the same traffic.<\/li>\n<li><strong>Gradual rollout.<\/strong> Start with 5% of traffic on the new platform, increase to 25%, 50%, then 100%.<\/li>\n<li><strong>Instant rollback.<\/strong> If the new platform has issues, route all traffic back to the old platform.<\/li>\n<li><strong>Attribution continuity.<\/strong> The old platform continues to resolve pending attributions while the new platform handles new ones.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Architecture: Two Platforms, One Domain<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The key to parallel running is traffic routing. Both platforms need to handle requests for your deep link domain, but each request should go to exactly one platform.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Option 1: DNS-Based Routing<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Route all traffic to one platform at a time. Simple, but does not support gradual rollout.<\/p>\n\n\n\n<pre><code>Phase 1: links.yourapp.com \u2192 old platform (100%)\nPhase 2: links.yourapp.com \u2192 new platform (100%)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is not true parallel running; it is a cutover. Use this only if you cannot implement traffic splitting.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Option 2: Reverse Proxy Routing<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Place a reverse proxy (Nginx, Caddy, Cloudflare Workers) in front of both platforms. The proxy decides which platform handles each request.<\/p>\n\n\n\n<pre><code>User \u2192 links.yourapp.com \u2192 Reverse Proxy\n                              \u251c\u2192 Old Platform (for existing links)\n                              \u2514\u2192 New Platform (for new links)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The proxy routes based on the link path:<\/p>\n\n\n\n<pre><code class=\"language-nginx\"># Nginx: route by path prefix\nlocation \/new\/ {\n    proxy_pass https:\/\/new-platform.example.com;\n}\n\nlocation \/ {\n    proxy_pass https:\/\/old-platform.example.com;\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This lets you create new links on the new platform (under <code>\/new\/<\/code>) while existing links continue to work on the old platform.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Option 3: Percentage-Based Traffic Splitting<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Split traffic by percentage using a load balancer or CDN edge worker:<\/p>\n\n\n\n<pre><code class=\"language-javascript\">\/\/ Cloudflare Worker: percentage-based routing\naddEventListener(&#39;fetch&#39;, event =&gt; {\n  event.respondWith(handleRequest(event.request));\n});\n\nasync function handleRequest(request) {\n  const url = new URL(request.url);\n\n  \/\/ Route new links (created on new platform) to new platform\n  if (isNewPlatformLink(url.pathname)) {\n    return fetch(`https:\/\/new-platform.example.com${url.pathname}${url.search}`, {\n      headers: request.headers,\n    });\n  }\n\n  \/\/ Route existing links to old platform\n  return fetch(`https:\/\/old-platform.example.com${url.pathname}${url.search}`, {\n    headers: request.headers,\n  });\n}\n\nfunction isNewPlatformLink(path) {\n  \/\/ Check against a list of paths created on the new platform\n  \/\/ or use a naming convention\n  return path.startsWith(&#39;\/v2\/&#39;) || newPlatformPaths.has(path);\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">SDK Coexistence<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Running two deep linking SDKs in the same app requires careful configuration to avoid conflicts.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Conflict Points<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Both SDKs will try to:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Handle incoming Universal Links \/ App Links.<\/strong> Both register for the same URL patterns.<\/li>\n<li><strong>Read the Install Referrer<\/strong> (Android). Only one SDK can read it successfully.<\/li>\n<li><strong>Intercept app open events.<\/strong> Both SDKs call their initialization logic on app launch.<\/li>\n<li><strong>Claim attribution.<\/strong> Both SDKs may try to attribute the same install.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">iOS: SceneDelegate Configuration<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">On iOS, use the SceneDelegate to route incoming links to the correct SDK:<\/p>\n\n\n\n<pre><code class=\"language-swift\">func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {\n    guard let url = userActivity.webpageURL else { return }\n\n    \/\/ New platform handles links with the new path structure\n    if isNewPlatformLink(url) {\n        TolinkuSDK.handleUniversalLink(url) { result in\n            self.routeDeepLink(result)\n        }\n        return\n    }\n\n    \/\/ Old platform handles legacy links\n    OldPlatformSDK.handleUniversalLink(url) { result in\n        self.routeDeepLink(result)\n    }\n}\n\nfunc isNewPlatformLink(_ url: URL) -&gt; Bool {\n    \/\/ Links created on the new platform use a specific path pattern\n    return url.path.hasPrefix(&quot;\/v2\/&quot;) || newPlatformPaths.contains(url.path)\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Android: Intent Filter Priority<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">On Android, you cannot have two SDKs both registered for the same domain in intent filters. Instead, handle all App Links in your Activity and delegate to the correct SDK:<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">class DeepLinkActivity : AppCompatActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        val uri = intent.data ?: return\n\n        if (isNewPlatformLink(uri)) {\n            TolinkuSDK.handleDeepLink(uri) { result -&gt;\n                routeDeepLink(result)\n            }\n        } else {\n            OldPlatformSDK.handleDeepLink(uri) { result -&gt;\n                routeDeepLink(result)\n            }\n        }\n    }\n\n    private fun isNewPlatformLink(uri: Uri): Boolean {\n        return uri.path?.startsWith(&quot;\/v2\/&quot;) == true ||\n               newPlatformPaths.contains(uri.path)\n    }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Install Referrer Handling<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">On Android, the <a href=\"https:\/\/developer.android.com\/google\/play\/installreferrer\" rel=\"nofollow noopener\" target=\"_blank\">Google Play Install Referrer API<\/a> can only be read reliably once. Configure the new SDK to read it first:<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">\/\/ Application.onCreate()\n\/\/ New SDK reads Install Referrer first\nTolinkuSDK.initialize(this, config = TolinkuConfig(\n    readInstallReferrer = true\n))\n\n\/\/ Old SDK: disable Install Referrer reading\nOldPlatformSDK.initialize(this, config = OldConfig(\n    readInstallReferrer = false,\n    attributionOnly = true\n))\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Verification Files During Parallel Running<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Both platforms may need to serve verification files for your domain. Since only one server can respond to requests for <code>\/.well-known\/<\/code> paths, you need to merge the verification files.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Merging apple-app-site-association<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The AASA file supports multiple <code>applinks<\/code> entries. Merge the entries from both platforms:<\/p>\n\n\n\n<pre><code class=\"language-json\">{\n  &quot;applinks&quot;: {\n    &quot;apps&quot;: [],\n    &quot;details&quot;: [\n      {\n        &quot;appIDs&quot;: [&quot;TEAMID.com.yourapp.ios&quot;],\n        &quot;components&quot;: [\n          { &quot;\/&quot;: &quot;\/v2\/*&quot; },\n          { &quot;\/&quot;: &quot;\/*&quot; }\n        ]\n      }\n    ]\n  }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Since both platforms serve links for the same app, the AASA file content should be identical. The critical fields are your Team ID and Bundle ID, which do not change between platforms.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Merging assetlinks.json<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Similarly, the <code>assetlinks.json<\/code> file contains your app&#39;s package name and signing certificate, which are the same regardless of platform:<\/p>\n\n\n\n<pre><code class=\"language-json\">[\n  {\n    &quot;relation&quot;: [&quot;delegate_permission\/common.handle_all_urls&quot;],\n    &quot;target&quot;: {\n      &quot;namespace&quot;: &quot;android_app&quot;,\n      &quot;package_name&quot;: &quot;com.yourapp.android&quot;,\n      &quot;sha256_cert_fingerprints&quot;: [\n        &quot;YOUR_SHA256_FINGERPRINT&quot;\n      ]\n    }\n  }\n]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If you are using the reverse proxy approach, serve these files directly from the proxy to ensure consistency.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Monitoring Parallel Operation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">During parallel running, monitor both platforms to compare performance and catch issues.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Key Metrics to Compare<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Metric<\/th>\n<th>Old Platform<\/th>\n<th>New Platform<\/th>\n<th>Acceptable Variance<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Click count<\/td>\n<td>Baseline<\/td>\n<td>Should match traffic split<\/td>\n<td>+\/- 5%<\/td>\n<\/tr>\n<tr>\n<td>Deep link open rate<\/td>\n<td>Baseline<\/td>\n<td>Should match or improve<\/td>\n<td>+\/- 10%<\/td>\n<\/tr>\n<tr>\n<td>Deferred link match rate<\/td>\n<td>Baseline<\/td>\n<td>Should match or improve<\/td>\n<td>+\/- 10%<\/td>\n<\/tr>\n<tr>\n<td>Verification file response time<\/td>\n<td>Baseline<\/td>\n<td>Should be &lt; 200ms<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td>Error rate<\/td>\n<td>Baseline<\/td>\n<td>Should be &lt; 1%<\/td>\n<td><\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Alerting<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Set up alerts for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Verification file errors.<\/strong> If <code>\/.well-known\/<\/code> paths return non-200 status codes, deep links will break on new installs.<\/li>\n<li><strong>Click spike differences.<\/strong> If the old platform shows 1000 clicks\/hour but the new platform shows 100, traffic routing is broken.<\/li>\n<li><strong>SDK crash rate increase.<\/strong> Monitor crash reporting (Firebase Crashlytics, Sentry) for new crashes related to the SDKs.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">The Cutover Plan<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Parallel running is temporary. Eventually, you need to cut over to the new platform entirely. Plan this in phases.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Phase 1: New Links Only (Week 1-2)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">All new campaigns, QR codes, and marketing links use the new platform. Existing links continue on the old platform.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Phase 2: Traffic Split (Week 3-4)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If using a reverse proxy, start routing a percentage of existing link traffic to the new platform. Increase gradually:<\/p>\n\n\n\n<pre><code>Week 3, Day 1: 10% of existing link traffic \u2192 new platform\nWeek 3, Day 3: 25% \u2192 new platform\nWeek 4, Day 1: 50% \u2192 new platform\nWeek 4, Day 3: 75% \u2192 new platform\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Monitor metrics at each step. If there is a significant regression, pause and investigate.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Phase 3: Full Cutover (Week 5)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Route 100% of traffic to the new platform. Keep the old platform running (but not serving traffic) for 1-2 weeks as a rollback option.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Phase 4: Cleanup (Week 6-8)<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Remove the old SDK from the app (after the attribution window expires).<\/li>\n<li>Shut down the reverse proxy (if used) and point DNS directly to the new platform.<\/li>\n<li>Cancel the old platform subscription (after verifying all redirects work).<\/li>\n<li>Archive the old platform&#39;s analytics data.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">When to Avoid Parallel Running<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Parallel running is not always the right approach:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Simple setups.<\/strong> If you have one app, a few dozen links, and low traffic, a direct cutover is fine. Test thoroughly on staging, then switch.<\/li>\n<li><strong>No custom domain.<\/strong> If your deep links use the old platform&#39;s subdomain (e.g., <code>yourapp.oldplatform.link<\/code>), you cannot split traffic. You need to create new links on the new platform and redirect old links.<\/li>\n<li><strong>Budget constraints.<\/strong> Running two platforms simultaneously means paying for both during the overlap period.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For these cases, a direct cutover with a solid rollback plan (see <a href=\"https:\/\/tolinku.com\/blog\/migration-risk-mitigation\/\">migration risk mitigation<\/a>) is the better approach.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Tolinku Parallel Running<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku<\/a> supports custom domains, which makes parallel running straightforward. Configure your routes in the <a href=\"https:\/\/tolinku.com\/docs\/\">Tolinku dashboard<\/a>, point your reverse proxy to Tolinku for new links, and gradually shift traffic.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For the migration timeline, see <a href=\"https:\/\/tolinku.com\/blog\/migrating-to-tolinku\/\">migrating to Tolinku<\/a>. For domain configuration, see <a href=\"https:\/\/tolinku.com\/blog\/link-redirect-during-migration\/\">link redirects during migration<\/a>. For attribution during parallel running, see <a href=\"https:\/\/tolinku.com\/blog\/migration-impact-attribution\/\">migration impact on attribution<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Run two deep linking platforms simultaneously during migration. Manage parallel operation, route traffic correctly, handle SDK coexistence, and plan the cutover.<\/p>\n","protected":false},"author":2,"featured_media":1403,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Parallel Running: Operating Two Deep Link Platforms","rank_math_description":"Run two deep linking platforms simultaneously during migration. Manage parallel operation, route traffic, and plan the cutover strategy.","rank_math_focus_keyword":"parallel running migration","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-parallel-running-migration.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-parallel-running-migration.png","footnotes":""},"categories":[17],"tags":[254,20,52,69,350,346,186,80],"class_list":["post-1404","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-comparisons","tag-best-practices","tag-deep-linking","tag-migration","tag-mobile-development","tag-parallel-running","tag-planning","tag-sdk","tag-testing"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1404","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=1404"}],"version-history":[{"count":4,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1404\/revisions"}],"predecessor-version":[{"id":2594,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1404\/revisions\/2594"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1403"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1404"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1404"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1404"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}