Deeplinks

Route Pulsate campaign links into your app, other apps, or phone numbers.

Deeplinks let you link from Pulsate to your app, other apps, or phone numbers. In the Pulsate CMS you can add buttons to your marketing campaigns that lead to deep links in your application.

❗️

Deeplinks must be unique

When creating deeplinks that target your app, pick a scheme / name that is unique. If several apps on the device share the same deeplink scheme, tapping the link makes the phone ask which app to open instead of opening yours directly.

Add / edit / delete deeplinks in the Pulsate CMS

The first step is adding deeplinks in the Pulsate CMS so you can use them in campaigns (https://control.pulsatehq.com).

  1. From the CMS dashboard, select your application and open Settings → App Settings.
  2. In App Settings you will find the Add Deeplink and Manage Deeplinks sections.
  3. To add a deeplink, enter its name and value and click Save.

The deeplink is now added to your account and available when building campaigns. Existing deeplinks can be changed or removed with the Edit and Delete buttons next to each entry.

📘

Deeplink to call a phone number

To open the Phone app with a number pre-filled, create a deeplink with a URL formatted as tel:+123456789. This opens the Phone app on both iOS and Android with +123456789 as the number to call.

Register your URL scheme in Xcode

To support deep linking, declare your app's URL scheme in Xcode:

  1. Select your project in Xcode and open the Info tab.
  2. Expand URL Types and add a new entry.
  3. Enter your bundle identifier in the Identifier field and the URL scheme you want to use.

You can test the scheme by entering myapp:// in the device's web browser.

How Pulsate opens a deeplink

When a user taps a campaign's deeplink - in a push, an in-app message, or the feed - Pulsate:

  1. Calls your link listener, if you registered one. If it returns true, Pulsate stops there.
  2. Otherwise asks iOS to open the link (UIApplication.shared.open).
  3. If the link uses your app's own scheme, iOS delivers it back to your app:
    • App without scenes: application(_:open:options:) on your app delegate.
    • Scene-based app: your scene delegate - see Scene-based apps.

You can handle your own links in either place: in the link listener (simplest, and it only sees Pulsate's links), or in your normal URL handling (which also sees links from Safari, other apps and QR codes).

Handling Pulsate deeplinks

Register a link listener on the PULPulsateManager instance. Pulsate invokes your listener when a campaign or CTA link needs to be handled. Return true if your app consumed the link, or false to let Pulsate open it with iOS as described above.

import PULPulsate

guard let manager = PULPulsateFactory.getDefaultInstance() else { return }

manager.setPULPulsateLinkListener { link -> Bool in
    if link == "myapp://clothes" {
        self.openClothesViewController()
        return true
    }
    return false
}

📘

Register the listener at startup

The listener receives the link as a String (PULPulsateLinkListener = (String) -> Bool). Register it once, right after PULPulsateFactory.getInstance(...) in application(_:didFinishLaunchingWithOptions:). A push tap that launches the app is handled shortly after launch; a listener registered later - for example after sign-in - misses it, and the link falls through to iOS.

Scene-based apps

If your app uses the UIScene lifecycle and handles its own links outside the link listener, handle them in your scene delegate - UIKit does not call application(_:open:options:) on the app delegate in a scene-based app. Implement both:

  • scene(_:openURLContexts:) - links that arrive while the app is running.
  • scene(_:willConnectTo:options:), reading connectionOptions.urlContexts - links that launch the app. Without this, a deeplink tapped while the app is closed is dropped.

Pulsate does not need these callbacks - they are only for routing your own links.

import UIKit

final class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    private var pendingLinks: [URL] = []

    func scene(
        _ scene: UIScene,
        willConnectTo session: UISceneSession,
        options connectionOptions: UIScene.ConnectionOptions
    ) {
        // A link that launches the app arrives here, not in scene(_:openURLContexts:).
        pendingLinks += connectionOptions.urlContexts.map(\.url)
    }

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        pendingLinks += URLContexts.map(\.url)
        if scene.activationState == .foregroundActive {
            routePendingLinks()
        }
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        // On a cold launch the window is not on screen yet in willConnectTo, and anything
        // presented then is discarded - so links wait until the scene is active.
        routePendingLinks()
    }

    private func routePendingLinks() {
        let links = pendingLinks
        pendingLinks.removeAll()
        for url in links where url.scheme == "myapp" {
            // Route to the screen for this link, e.g. myapp://clothes
        }
    }
}

Testing checklist

Test each deeplink in all three app states:

  • App in the foreground
  • App in the background
  • App closed (swipe it away first), then tap the push - the most timing-sensitive case