Skip to main content

iOS Universal Links Setup

Universal Links let your iOS app open when a user taps an OpenLynk link, bypassing Safari entirely. This guide walks through every step from dashboard configuration to on-device testing.

Prerequisites

Before you begin, make sure you have:

  • An iOS app with a valid Bundle ID (e.g. com.example.myapp)
  • Your Apple Developer Team ID (found in Apple Developer under Membership)
  • Your app registered in the OpenLynk dashboard with iOS settings configured
info

Universal Links require HTTPS and a properly served Apple App Site Association (AASA) file. OpenLynk handles both of these for you automatically.

Step 1: Configure Your App in OpenLynk

In the OpenLynk dashboard, go to your app and open Settings. Fill in the iOS fields:

FieldExampleDescription
iOS Bundle IDcom.example.myappMust match your Xcode project exactly
iOS Team IDA1B2C3D4E5Found in Apple Developer under Membership
iOS App Store URLhttps://apps.apple.com/app/id123456789Fallback when the app is not installed

Once saved, OpenLynk automatically generates and hosts the AASA file at:

https://YOUR_APP_SLUG.openlynk.to/.well-known/apple-app-site-association
tip

You do not need to create, host, or maintain the AASA file yourself. OpenLynk keeps it in sync whenever you update your iOS settings.

Step 2: Add Associated Domains in Xcode

  1. Open your project in Xcode.
  2. Select your app target, then go to Signing & Capabilities.
  3. Click + Capability and add Associated Domains.
  4. Add your OpenLynk domain:
applinks:YOUR_APP_SLUG.openlynk.to

If you are using a custom domain, add that as well:

applinks:links.mycompany.com
warning

The domain must match exactly, including case. Do not include https:// or a trailing slash.

When a user taps a Universal Link, iOS delivers it to your app through SceneDelegate (iOS 13+) or AppDelegate. You must pass the incoming URL to the OpenLynk SDK so it can resolve the link and call your onDeepLink callback.

With SceneDelegate (iOS 13+)

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var openlynkSDK: OpenlynkSDK!

func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = scene as? UIWindowScene else { return }

// Initialize SDK
openlynkSDK = OpenlynkSDK(
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
config: OpenlynkSDKConfig(
onDeepLink: { [weak self] parsed in
self?.navigateTo(
path: parsed.destinationPath,
params: parsed.parameters
)
}
)
)
openlynkSDK.initSDK()

// Handle Universal Link from cold start
if let userActivity = connectionOptions.userActivities.first,
userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
openlynkSDK.handleIncomingURL(url)
}

let window = UIWindow(windowScene: windowScene)
window.rootViewController = UINavigationController(
rootViewController: HomeViewController()
)
self.window = window
window.makeKeyAndVisible()
}

// Universal Link — app already running in background
func scene(_ scene: UIScene,
continue userActivity: NSUserActivity) {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return }
openlynkSDK.handleIncomingURL(url)
}

// URL scheme or other URL contexts
func scene(_ scene: UIScene,
openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
openlynkSDK.handleIncomingURL(url)
}

private func navigateTo(path: String, params: [String: Any]) {
// Your navigation logic
}
}

With AppDelegate (Pre-iOS 13)

If your app does not use SceneDelegate:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var openlynkSDK: OpenlynkSDK!

func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
openlynkSDK = OpenlynkSDK(
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
config: OpenlynkSDKConfig(
onDeepLink: { parsed in /* navigate */ }
)
)
openlynkSDK.initSDK()
return true
}

func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return false }
openlynkSDK.handleIncomingURL(url)
return true
}
}

Step 4: Verify the AASA File

OpenLynk generates the AASA file automatically. Verify it is accessible by visiting:

https://YOUR_APP_SLUG.openlynk.to/.well-known/apple-app-site-association

Confirm the following:

  • The page loads with an HTTP 200 status
  • The response is valid JSON
  • The appID value matches {TeamID}.{BundleID} exactly (e.g. A1B2C3D4E5.com.example.myapp)

You can also use Apple's official validation tool: https://search.developer.apple.com/appleassociateddomainsverification/

Step 5: Test on a Physical Device

caution

Universal Links do not work in the iOS Simulator. You must test on a real device.

  1. Open the Notes app on your device.
  2. Type or paste: https://YOUR_APP_SLUG.openlynk.to/test-link
  3. Long-press the link.
  4. You should see "Open in [Your App]" in the context menu.
  1. Generate a real link via the SDK or the OpenLynk dashboard.
  2. Send it to yourself via Messages or Mail.
  3. Tap the link.
  4. Your app should open directly (not Safari).
  5. Verify your onDeepLink callback fires with the correct destination.

Test from Safari

  1. Navigate to your link URL in Safari.
  2. If a banner appears at the top, tap "Open".
  3. Alternatively, use the Share Sheet and select your app.

Troubleshooting

CauseFix
Associated Domains not configuredAdd applinks:YOUR_APP_SLUG.openlynk.to in Xcode
Bundle ID mismatchEnsure the Xcode Bundle ID matches the dashboard value exactly
AASA file inaccessibleVisit the AASA URL in a browser and confirm a valid JSON response
Stale AASA cacheDelete and reinstall the app. iOS caches AASA for up to 24 hours
In-app browserUniversal Links do not work inside Instagram, Facebook, or Twitter browsers

"Open in App" option does not appear

  • Delete the app and reinstall it.
  • Wait a few minutes after install (iOS fetches the AASA file asynchronously).
  • Ensure the link is typed or pasted, not auto-linked text.
  • Try from the Notes or Messages app (not the Safari URL bar).

App opens but does not navigate

  • Verify your handleIncomingURL implementation is being called.
  • Add logging to confirm the URL is passed to the SDK.
  • Check that the link exists in the OpenLynk dashboard.

Works in development, fails in production

  • Ensure the production build has the Associated Domains capability enabled.
  • Verify the production AASA file references the correct Bundle ID.
  • Confirm the provisioning profile includes the Associated Domains entitlement.

Checklist

  • Bundle ID in Xcode matches the OpenLynk dashboard
  • Team ID entered in the OpenLynk dashboard
  • Associated Domains capability added in Xcode
  • applinks:YOUR_APP_SLUG.openlynk.to added to associated domains
  • AASA file accessible (HTTP 200, valid JSON)
  • appID in AASA matches {TeamID}.{BundleID}
  • handleIncomingURL called in SceneDelegate or AppDelegate
  • Tested on a physical device (not the simulator)
  • Link shows "Open in App" in Notes
  • Link opens app directly from Messages
  • Navigation works from cold start, background, and foreground

What's Next?