---
title: Link Interception
slug: web-view-sdk-link-interception
createdAt: 2026-11-03T10:14:18.663Z
---

No link opens inside the chat. When someone taps an `http`, `https`, `mailto`, `tel` or `sms` link, the SDK opens it outside the app: in the browser, or in the app that handles the scheme, such as Mail, Phone or Messages. Links with any other scheme are blocked. To handle some links in your app instead, set a link delegate

## Set a delegate

::::Tabs
:::Tab{title="iOS"}
```swift
final class ChatLinks: KindlyWebViewClientDelegate {
    func shouldHandleLink(url: URL) async -> Bool {
        guard url.host == "myapp.example" else { return true }
        await MainActor.run {
            // open it in your app
        }
        return false
    }
}

let chatLinks = ChatLinks()
KindlyWebViewSDK.delegate = chatLinks
```

The SDK holds the delegate weakly, so keep a reference to it. The method is `async`, and a synchronous implementation satisfies the protocol too. Do UI work on the main actor, as above
:::

:::Tab{title="Android"}
```kotlin
KindlyWebViewSDK.`interface` = object : KindlyWebViewInteraction {
    override suspend fun shouldHandleLink(url: String): Boolean {
        if (!url.startsWith("https://myapp.example/")) return true
        openInApp(url)
        return false
    }
}
```

The SDK calls `shouldHandleLink` on the main thread, so suspend for slow work instead of blocking. An exception counts as `true`. Set `interface` to `null` to remove the delegate
:::

:::Tab{title="Flutter"}
```dart
await KindlyWebViewSDK.setDelegate(shouldHandleLink: (String url) async {
  if (url.startsWith('https://myapp.example/')) {
    openInApp(url);
    return false; // handled by the app
  }
  return true; // the SDK opens it
});
```

An answer that throws counts as `true`. Remove the delegate with `clearDelegate()`. The callback reaches the Flutter engine whose Dart code last called `start`, see [Initialization](docId\:MqmlgrN2Nv8ye8otUkYqC)
:::

:::Tab{title="React Native"}
```typescript
KindlyWebViewSDK.setDelegate({
  shouldHandleLink: async (url) => {
    if (url.startsWith('https://myapp.example/')) {
      openInApp(url);
      return false; // handled by the app
    }
    return true; // the SDK opens it
  },
});
```

Only an explicit `false` keeps the link in your app, and an answer that throws counts as `true`. `setDelegate(null)` removes the delegate set here and leaves one your app set natively alone. After a JavaScript reload, call `setDelegate` again
:::
::::

Return `true` to let the SDK open the link, or `false` to handle it yourself. The delegate stays set across `start` and `kill`

## Timeouts

The SDK waits 5 seconds for an answer, and no answer counts as `true`. The Flutter and React Native packages stop waiting for your callback after 4 seconds (`KindlyWebViewSDK.linkCallbackTimeout` in Flutter, `KindlyWebViewSDK.linkCallbackTimeoutMs` in React Native), so their answer always reaches the native SDK in time. On Android, React Native pauses JavaScript timers while the chat screen is in front, so the native 5 seconds apply there, see [Authentication](docId\:ObaH52zBKe08oBMXxiXu3)

## Which links reach the delegate

The SDK asks the delegate only about links it would open, so the rules below apply first:

- Links open only while the chat is displayed
- A custom scheme, such as `myapp://orders`, is blocked and never reaches the delegate. To route a bot link into your app, link to an `https` address on your domain and intercept it here
- The SDK opens at most three links in 10 seconds and ignores a second tap on the same link within 1.5 seconds

The SDK cancels the web view's navigation before it asks, so a slow answer never holds the chat
