{"id":472,"date":"2026-03-17T17:00:00","date_gmt":"2026-03-17T22:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=472"},"modified":"2026-03-07T04:35:15","modified_gmt":"2026-03-07T09:35:15","slug":"universal-links-and-redirects","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/universal-links-and-redirects\/","title":{"rendered":"Universal Links and Redirects: Why They Break and How to Fix Them"},"content":{"rendered":"\n<p>You set up Universal Links correctly. The <code>apple-app-site-association<\/code> (AASA) file is valid JSON, it is served at the right path, your app entitlements are correct, and testing with a direct link works perfectly. Then you drop the URL into your email campaign, wrap it in a click tracker, and suddenly the link opens Safari instead of your app.<\/p>\n\n\n\n<p>This is one of the most common Universal Links problems in production, and it comes down to a single rule that Apple does not make obvious: <strong>iOS checks the domain of the URL it initially receives, not the domain of the final destination after redirects.<\/strong><\/p>\n\n\n\n<p>This guide explains why that matters, walks through the scenarios where it causes failures, and covers every practical solution. If you need a refresher on Universal Links fundamentals, start with our <a href=\"https:\/\/tolinku.com\/blog\/universal-links-everything-you-need-to-know\/\">complete Universal Links guide<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Core Problem: iOS Reads the First Domain, Not the Last<\/h2>\n\n\n\n<p>When a user taps a Universal Link, iOS checks its locally cached copy of the AASA file for the domain in that URL. If the AASA file is registered for that domain and the path matches, iOS opens your app. If not, it falls through to Safari.<\/p>\n\n\n\n<p>Here is what iOS does not do: follow HTTP redirects and check the AASA file at the final destination. The Universal Link check happens at the very first URL, before any redirect occurs.<\/p>\n\n\n\n<p>This behavior is <a href=\"https:\/\/developer.apple.com\/documentation\/xcode\/supporting-universal-links-in-your-app\" rel=\"nofollow noopener\" target=\"_blank\">documented by Apple<\/a>, though it is easy to miss in the middle of a longer setup guide.<\/p>\n\n\n\n<p>So if the tap URL is <code>https:\/\/click.emailprovider.com\/track?url=https:\/\/yourdomain.com\/product\/123<\/code>, iOS looks for an AASA file on <code>click.emailprovider.com<\/code>. That domain almost certainly has no AASA file, or its AASA file has no entry for your app. iOS falls back to loading the URL in Safari. Safari follows the redirect, lands on your domain, and your app never opens.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Common Redirect Chain Scenarios<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Email Click Trackers<\/h3>\n\n\n\n<p>Email marketing platforms (Mailchimp, Klaviyo, Brevo, Iterable, and others) wrap every link in your email with their own tracking URL. Our article on <a href=\"https:\/\/tolinku.com\/blog\/universal-links-in-email\/\">Universal Links in email<\/a> covers platform-specific workarounds for the most popular email services. The real destination is encoded as a query parameter. When the recipient taps the link, the platform records the click and then issues a redirect to your URL.<\/p>\n\n\n\n<p>The tap URL looks like:<\/p>\n\n\n\n<pre><code>https:\/\/click.mailchimp.com\/track\/click\/XXXXXXXX\/yourdomain.com?p=eyJzIjoiX1...\n<\/code><\/pre>\n\n\n\n<p>iOS sees <code>click.mailchimp.com<\/code> and checks for an AASA file there. There is none that covers your app. Universal Link fails.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">URL Shorteners<\/h3>\n\n\n\n<p>Services like Bitly, t.co, short.io, and similar shorteners produce URLs on their own domains. Tapping <code>https:\/\/bit.ly\/3xYzAbc<\/code> means iOS checks <code>bit.ly<\/code> for your AASA. This will never work unless you control the short domain and host your AASA file there.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Marketing Attribution Platforms<\/h3>\n\n\n\n<p>Any platform that wraps links to record UTM parameters, device fingerprints, or attribution data runs the same redirect pattern. The click goes to the platform&#39;s domain first, gets recorded, and then redirects to your destination.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">CDN and Load Balancer Redirects<\/h3>\n\n\n\n<p>A subtler case: your own infrastructure redirecting between domains or subdomains. If your CDN redirects <code>www.yourdomain.com<\/code> to <code>yourdomain.com<\/code> (or vice versa), the AASA file must be configured correctly on both the initial domain iOS sees and the final domain. Both hostnames need entries in your app&#39;s Associated Domains entitlement.<\/p>\n\n\n\n<p>Similarly, HTTP-to-HTTPS redirects. If someone taps an <code>http:\/\/<\/code> link, iOS will not apply Universal Links to it at all. Universal Links only activate for <code>https:\/\/<\/code> URLs.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Vanity Domains and Custom Short URLs<\/h3>\n\n\n\n<p>Marketing teams often use branded short domains like <code>go.yourcompany.com<\/code> or <code>links.yourapp.com<\/code>. These are great for branding, but they need their own AASA file and their own Associated Domains entry in your app. Many teams set up the redirect but forget the AASA registration.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Solutions<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Option 1: Register AASA on Every Domain in the Chain<\/h3>\n\n\n\n<p>If you control every domain in the redirect chain, you can host an AASA file on each one and add each domain to your app&#39;s Associated Domains entitlement.<\/p>\n\n\n\n<p>For a branded short domain like <code>go.yourcompany.com<\/code>, this is the clean solution:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Host your AASA file at <code>https:\/\/go.yourcompany.com\/.well-known\/apple-app-site-association<\/code><\/li>\n<li>Add <code>applinks:go.yourcompany.com<\/code> to your app&#39;s Associated Domains entitlement<\/li>\n<li>Configure the short link to redirect to <code>https:\/\/yourdomain.com\/path<\/code><\/li>\n<\/ol>\n\n\n\n<p>The first URL iOS sees is <code>go.yourcompany.com<\/code>, it finds the AASA file, and it opens your app directly without following the redirect at all. For a complete walkthrough of setting up and validating the AASA file, see our <a href=\"https:\/\/tolinku.com\/blog\/aasa-file-setup\/\">AASA file setup guide<\/a>.<\/p>\n\n\n\n<p>Example AASA for the short domain:<\/p>\n\n\n\n<pre><code class=\"language-json\">{\n  &quot;applinks&quot;: {\n    &quot;details&quot;: [\n      {\n        &quot;appIDs&quot;: [&quot;TEAMID.com.yourcompany.app&quot;],\n        &quot;components&quot;: [\n          { &quot;\/&quot;: &quot;\/*&quot; }\n        ]\n      }\n    ]\n  }\n}\n<\/code><\/pre>\n\n\n\n<p>You do not need to reproduce your full path configuration on the short domain. A wildcard <code>\/*<\/code> works because iOS opens the app for any path, and your app receives the short URL. Your app (or your deep link routing logic) can then resolve the short URL to the full destination path.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Option 2: Use Direct Links Without Redirect Wrapping<\/h3>\n\n\n\n<p>For email campaigns, some platforms let you disable click tracking for specific links, or they let you use your own tracking domain. If you can serve the tracking URL from your own domain (say, <code>track.yourdomain.com<\/code>) and AASA-register that domain, the link will work.<\/p>\n\n\n\n<p>Some platforms support &quot;branded tracking domains&quot; specifically for this purpose. The setup varies by platform, but the concept is the same: the click tracker runs on a domain you control, you host the AASA there, and iOS sees your domain first.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Option 3: The JavaScript Redirect Workaround<\/h3>\n\n\n\n<p>If you cannot control the redirect chain at all, a JavaScript-based redirect can help in limited cases. Instead of a server-side HTTP 301\/302 redirect, the intermediate page loads in Safari and runs JavaScript that sets <code>window.location<\/code> to your Universal Link target.<\/p>\n\n\n\n<p>The reason this sometimes works is that iOS evaluates Universal Links when a tap originates from certain contexts (like Safari links to other domains), but same-domain navigation in Safari does not always trigger the Universal Link check. The JavaScript approach changes the navigation context.<\/p>\n\n\n\n<p>However, this is fragile. Whether the Universal Link activates depends on how the navigation was initiated and which iOS version is running. It also requires a real web page to load before the redirect fires, adding latency. Using <a href=\"https:\/\/tolinku.com\/blog\/sfsafariviewcontroller-deep-links\/\">SFSafariViewController for deep links<\/a> instead of WKWebView can help in some in-app browser scenarios, but for general redirect chains, treat JavaScript redirects as a last resort, not a reliable production strategy.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Option 4: Accept the Fallback and Handle It on the Web<\/h3>\n\n\n\n<p>For cases you cannot fix on the link side (third-party click trackers you cannot control), design your web fallback page to do the work.<\/p>\n\n\n\n<p>When the link opens in Safari and lands on your web page, you can use the <a href=\"https:\/\/developer.apple.com\/documentation\/webkit\/promoting_apps_with_smart_app_banners\" rel=\"nofollow noopener\" target=\"_blank\">Smart App Banner<\/a> meta tag to prompt users to open the app, or you can use JavaScript to attempt a custom URI scheme open first, then fall back to the App Store. This is not as seamless as a Universal Link opening the app directly, but it recovers the session.<\/p>\n\n\n\n<pre><code class=\"language-html\">&lt;meta name=&quot;apple-itunes-app&quot; content=&quot;app-id=YOUR_APP_STORE_ID, app-argument=https:\/\/yourdomain.com\/path&quot;&gt;\n<\/code><\/pre>\n\n\n\n<p>Platforms like <a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku<\/a> handle this fallback logic automatically, including deferred deep linking so users land on the right screen after installing the app.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Server-Side vs. Client-Side Redirects<\/h2>\n\n\n\n<p>The distinction matters for Universal Links. A server-side redirect is an HTTP 301 or 302 response. The browser (or iOS) receives the response, reads the <code>Location<\/code> header, and makes a new request to the redirect target. iOS evaluates the Universal Link check on the initial URL before any server response arrives.<\/p>\n\n\n\n<p>A client-side redirect is HTML or JavaScript that fires after the page loads (<code>&lt;meta http-equiv=&quot;refresh&quot;&gt;<\/code>, <code>window.location =<\/code>, etc.). The initial request loads a real page, and only then does the redirect occur.<\/p>\n\n\n\n<p>For Universal Links, both cases result in the same problem: iOS checked the original domain before anything else happened. The type of redirect does not change that.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testing Redirect Chains<\/h2>\n\n\n\n<p>Before debugging iOS behavior, map out the full redirect chain so you know exactly what iOS is seeing.<\/p>\n\n\n\n<p>Use <code>curl<\/code> with the <code>-L<\/code> flag to follow redirects and the <code>-I<\/code> flag to print headers only:<\/p>\n\n\n\n<pre><code class=\"language-bash\">curl -ILv &quot;https:\/\/click.emailprovider.com\/track?url=https:\/\/yourdomain.com\/product\/123&quot;\n<\/code><\/pre>\n\n\n\n<p>The <code>-v<\/code> verbose flag shows each request and response header. You will see each <code>Location:<\/code> header and can trace the full chain from the initial URL to the final destination.<\/p>\n\n\n\n<p>A cleaner view using just the effective URL:<\/p>\n\n\n\n<pre><code class=\"language-bash\">curl -Ls -o \/dev\/null -w &quot;%{url_effective}&quot; &quot;https:\/\/your-short-link.com\/abc&quot;\n<\/code><\/pre>\n\n\n\n<p>To see every hop explicitly:<\/p>\n\n\n\n<pre><code class=\"language-bash\">curl -v -L --max-redirs 10 &quot;https:\/\/your-short-link.com\/abc&quot; 2&gt;&amp;1 | grep -E &quot;^(&gt; GET|&lt; HTTP|&lt; Location)&quot;\n<\/code><\/pre>\n\n\n\n<p>This gives you output like:<\/p>\n\n\n\n<pre><code>&gt; GET \/abc HTTP\/2\n&lt; HTTP\/1.1 301 Moved Permanently\n&lt; Location: https:\/\/click.tracker.com\/r?url=https%3A%2F%2Fyourdomain.com%2Fproduct%2F123\n&gt; GET \/r?url=... HTTP\/2\n&lt; HTTP\/1.1 302 Found\n&lt; Location: https:\/\/yourdomain.com\/product\/123\n&gt; GET \/product\/123 HTTP\/2\n&lt; HTTP\/1.1 200 OK\n<\/code><\/pre>\n\n\n\n<p>Once you have the full chain, you know which domains need AASA files. For each domain in the chain (except the final one if iOS is opening the app before following redirects), check whether it has a valid AASA:<\/p>\n\n\n\n<pre><code class=\"language-bash\">curl -s &quot;https:\/\/click.tracker.com\/.well-known\/apple-app-site-association&quot; | python3 -m json.tool\n<\/code><\/pre>\n\n\n\n<p>If that returns a 404 or malformed JSON, that domain is the break point.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Verifying Your AASA Registration<\/h2>\n\n\n\n<p>Apple&#39;s CDN caches AASA files, so changes take time to propagate. Use the <a href=\"https:\/\/branch.io\/resources\/aasa-validator\/\" rel=\"nofollow noopener\" target=\"_blank\">Apple App Site Association validator<\/a> with caution (it is a competitor tool), or test directly using Apple&#39;s CDN endpoint:<\/p>\n\n\n\n<pre><code class=\"language-bash\">curl -s &quot;https:\/\/app-site-association.cdn-apple.com\/a\/v1\/yourdomain.com&quot; | python3 -m json.tool\n<\/code><\/pre>\n\n\n\n<p>This is the exact file Apple&#39;s servers have cached for your domain. If this does not match what you have at <code>\/.well-known\/apple-app-site-association<\/code>, Apple has not picked up your changes yet. Updates typically propagate within 24 hours after a new app install or update.<\/p>\n\n\n\n<p>For testing your full link before publishing a campaign, use the <a href=\"https:\/\/developer.apple.com\/documentation\/xcode\/testing-universal-links-in-your-app\" rel=\"nofollow noopener\" target=\"_blank\">Apple WWDC Universal Links test flow<\/a>: paste the final tap URL into Apple Notes on a real device and long-press the link. The context menu will show &quot;Open in Safari&quot; if Universal Links are not activating, or your app name if they are. This bypasses some of the redirect complexity and lets you verify that the AASA is correctly registered.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">A Practical Checklist<\/h2>\n\n\n\n<p>When a Universal Link is opening Safari instead of your app through a redirect chain, work through this in order:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Use <code>curl -ILv<\/code> to map the full redirect chain<\/li>\n<li>Identify the first domain iOS will see (the tap URL)<\/li>\n<li>Check whether that domain has a valid AASA file with your app&#39;s Team ID and bundle ID<\/li>\n<li>Check whether that domain is listed in your app&#39;s Associated Domains entitlement (<code>applinks:that-domain.com<\/code>)<\/li>\n<li>Verify Apple&#39;s CDN has the AASA by fetching <code>https:\/\/app-site-association.cdn-apple.com\/a\/v1\/that-domain.com<\/code><\/li>\n<li>If you cannot put an AASA on that domain, look for a way to eliminate the redirect (direct links, branded tracking domain, or disabling click wrapping)<\/li>\n<li>If none of that is possible, build a web fallback that recovers the session<\/li>\n<\/ol>\n\n\n\n<p>Redirect chains are one of the most common reasons Universal Links break in production. The fix is almost always the same: get your AASA on the domain iOS sees first. Once you understand that rule, debugging these failures becomes straightforward.<\/p>\n\n\n\n<p>For more on Universal Links configuration and troubleshooting, see the <a href=\"https:\/\/tolinku.com\/docs\/troubleshooting\/ios\/\">Tolinku iOS troubleshooting guide<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Understand why HTTP redirects break Universal Links. Learn workarounds for server-side redirects, link tracking services, and URL shorteners.<\/p>\n","protected":false},"author":2,"featured_media":471,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Universal Links and Redirects: Why They Break and How to Fix Them","rank_math_description":"Understand why HTTP redirects break Universal Links. Learn workarounds for server-side redirects, link tracking services, and URL shorteners.","rank_math_focus_keyword":"universal links redirects","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-universal-links-and-redirects.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-universal-links-and-redirects.png","footnotes":""},"categories":[12],"tags":[32,20,82,24,88,87,22,89],"class_list":["post-472","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ios","tag-apple-app-site-association","tag-deep-linking","tag-email-marketing","tag-ios","tag-redirects","tag-troubleshooting","tag-universal-links","tag-url-shorteners"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/472","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=472"}],"version-history":[{"count":4,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/472\/revisions"}],"predecessor-version":[{"id":2779,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/472\/revisions\/2779"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/471"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=472"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=472"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=472"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}