Handle offsite payments in your app
Some payment providers finalize transactions by redirecting buyers to secure HTTPS web pages, while others use deep linking to send buyers to external banking apps. To enhance the user experience for the latter scenario, you can configure your storefront to support Universal Links in iOS and App Links in Android. This allows users to be redirected back to your app after their payment is completed.
By default, Checkout Sheet Kit includes the lifecycle methods to manage deep-linking interactions. The following code ensures that external links, such as HTTPS and Deep Links, are handled gracefully by the OS.
// Default implementation in Checkout Sheet Kit
public func checkoutDidClickLink(url: URL) {
// Check if the URL can be opened by iOS
if UIApplication.shared.canOpenURL(url) {
// Open the URL
UIApplication.shared.open(url)
}
}
// Default implementation in Checkout Sheet Kit
override fun onCheckoutLinkClicked(uri: Uri) {
// Handle different URI schemes
when (uri.scheme) {
"tel" -> context.launchPhoneApp(uri.schemeSpecificPart)
"mailto" -> context.launchEmailApp(uri.schemeSpecificPart)
"https", "http" -> context.launchBrowser(uri)
// Non-matching URIs will be treated as deep links
else -> context.tryLaunchDeepLink(uri)
}
}
Anchor to RequirementsRequirements
- You have a custom domain connected to your storefront.
- Your custom domain uses SSL (HTTPS).
- You have a custom Shopify app with access to the Storefront API.
Anchor to Step 1: Configure your storefrontStep 1: Configure your storefront
You can set up your storefront to support Universal Links for iOS and App Links for Android, and re-route storefront URLs to your mobile app.
- Go to your storefront's admin panel.
- Click the name of your app.
- In your app's Configuration tab, click Edit on the Storefront API integration section.
- Set up Universal Links for iOS or App Links for Android:
- Universal Links for iOS: In the iOS Buy SDK section, enter your Apple App ID, enable Use iOS Universal Links, and upload your Apple app certificate.
- App Links for Android: In the Android Buy SDK section, provide your Android application ID and SHA256 fingerprint certificate values from the Play Store.
Configuring Universal Links or App Links for your store generates two .well-known
JSON files on your public storefront domain:
-
apple-app-site-association
for iOS -
assetlinks.json
for AndroidBoth operating systems periodically retrieve these files to ensure URLs open directly in your app, rather than in a web browser.
{
"applinks": {
"apps": [],
"details": [
{
"appID": "{APPLE_TEAM_ID}.{BUNDLE_IDENTIFIER}",
"paths": [
"NOT /admin/*",
"NOT /*/amazon_payments/callback",
"NOT /*/checkouts/*/express/callback",
"/*"
]
}
]
}
}
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "{PACKAGE_NAME}",
"sha256_cert_fingerprints": [
"{SHA256_FINGERPRINT}"
]
}
}
]
.well-known/*
files aren't served from "*.myshopify.com" domains.
You must connect a custom domain under https://admin.shopify.com/settings/domains
to serve these files.
The /*
catch-all route in the apple-app-site-association
configuration indicates that iOS should open any URL from your domain in your app, except for those paths specifically excluded by "NOT" rules. Similarly, the delegate_permission/common.handle_all_urls
relation in the assetlinks.json
does the equivalent for Android.
- Email Links: Links in emails, such as abandoned carts, will open in your app when clicked.
- Shared Links: General web links from your domain will open in your app unless they're specifically excluded.
- Webview Links: Links opened in a webview won't trigger the app unless it's set up to support them.
- Excluded Paths: (iOS only) URLs defined with a
NOT
clause in theapple-app-site-association
file will open outside the app as specified.
Anchor to Step 2: Enable Universal Links or set up App LinkingStep 2: Enable Universal Links or set up App Linking
The following section describes the steps to enable Universal Links in iOS and set up App Linking in Android.
Anchor to Enable Universal Links in iOSEnable Universal Links in i OS
To enable Universal Links in your iOS app, you'll need to configure an entitlements
file which specifies the associated domains that your app is allowed to handle:
- Launch Xcode and open your project or workspace file.
- Click on your project in the Project Navigator.
- Select your app target from the list of targets.
- Click on the Capabilities tab at the top of the editor.
- In the Capabilities section, find Associated Domains and toggle the switch to enable it. A new section should appear to configure your associated domains.
- Add each domain you want to associate with your app in the format
applinks:yourdomain.com
. The following example shows an example XML entitlements file:
Example.entitlements
Anchor to Set up App Linking in AndroidSet up App Linking in Android
To set up App Linking for Android, you need to:
- Configure your
AndroidManifest.xml
to specify intents to allow the app to handle App Links by specifying intent filters for the supported URL schemes and hosts you wish to capture. - Update
MainActivity.kt
to handle these intents.
AndroidManifest.xml
Anchor to React NativeReact Native
The same steps from the iOS and Android sections apply to React Native. Ensure you have configured both the App.entitlements
file for Swift and AndroidManifest.xml
file for Android.
Anchor to Step 3: Handle Universal Links or App Links in your appStep 3: Handle Universal Links or App Links in your app
The following section describes how to update your application entrypoint to route requests to the appropriate screen in your app.
For example, you might choose to open "/checkout" URLs in the Checkout Sheet Kit, route "/cart" requests to a cart screen or sheet or route "/product" requests to a product detail page.
If there are specific URLs in your app which you do not plan to support, it is important to re-route these unhandled routes back to a mobile browser. This will ensure customers are always able to complete their journey without encountering dead ends.
// App.swift
import SwiftUI
import ShopifyCheckoutSheetKit
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
// Handle Universal Links
handleUniversalLink(url: url)
}
}
}
private func handleUniversalLink(url: URL) {
let storefrontUrl = StorefrontURL(from: url)
switch true {
// Handle checkout URLs
case storefrontUrl.isCheckout() && !storefrontUrl.isThankYouPage():
presentCheckout(url)
// Handle cart URLs
case storefrontUrl.isCart():
navigateToCart()
// Fallback: Re-route everything else to Safari
default:
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}
}
}
}
struct ContentView: View {
var body: some View {
Text("Hello, World!")
}
}
public struct StorefrontURL {
public let url: URL
init(from url: URL) {
self.url = url
}
public func isThankYouPage() -> Bool {
return url.path.range(of: "/thank[-_]you", options: .regularExpression) != nil
}
public func isCheckout() -> Bool {
return url.path.contains("/checkout")
}
public func isCart() -> Bool {
return url.path.contains("/cart")
}
}
// AppDelegate.swift
import UIKit
import ShopifyCheckoutSheetKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// Only handle universal links from known activity types.
// You might also guard against your storefront domain here for additional security.
if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
handleUniversalLink(url: url)
return true
}
return false
}
private func handleUniversalLink(url: URL) {
let storefrontUrl = StorefrontURL(from: url)
switch true {
// Handle checkout URLs
case storefrontUrl.isCheckout() && !storefrontUrl.isThankYouPage():
presentCheckout(url)
// Handle cart URLs
case storefrontUrl.isCart():
navigateToCart()
/// Fallback: Re-route everything else to Safari
default:
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}
}
}
}
public struct StorefrontURL {
public let url: URL
init(from url: URL) {
self.url = url
}
public func isThankYouPage() -> Bool {
return url.path.range(of: "/thank[-_]you", options: .regularExpression) != nil
}
public func isCheckout() -> Bool {
return url.path.contains("/checkout")
}
public func isCart() -> Bool {
return url.path.contains("/cart")
}
}
// MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
App()
}
// Handle the intent that started the activity
handleIntent(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
// Handle new intents
handleIntent(intent)
}
private fun handleIntent(intent: Intent) {
// Ensure the intent action is VIEW
if (intent.action == Intent.ACTION_VIEW) {
intent.data?.let { uri ->
if (uri.path == "/cart") {
// Navigate to the cart screen
navigateToCart()
}
}
}
}
private fun navigateToCart() {
// ... implementation for navigating to the cart screen
}
}
// src/App.tsx
import { Linking } from 'react-native';
import { useShopifyCheckoutSheet } from '@shopify/checkout-sheet-kit';
// Hook for managing URLs that were used to open the app
const useInitialURL = (): {url: string | null} => {
const [url, setUrl] = useState<string | null>(null);
useEffect(() => {
const getUrlAsync = async () => {
// Get the deep link used to open the app
const initialUrl = await Linking.getInitialURL();
if (initialUrl !== url) {
setUrl(initialUrl);
}
};
getUrlAsync();
}, [url]);
return {
url,
};
};
function Routes() {
const shopify = useShopifyCheckoutSheet();
// Optional: `useNavigation` comes from `react-native-navigation`
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
const {url: initialUrl} = useInitialURL();
useEffect(() => {
async function handleUniversalLink(url: string) {
const storefrontUrl = new StorefrontURL(url);
switch (true) {
// Handle checkout URLs
case storefrontUrl.isCheckout() && !storefrontUrl.isThankYouPage():
shopify.present(url);
return;
// Handle cart URLs
case storefrontUrl.isCart():
navigation.navigate('Cart');
return;
}
const canOpenUrl = await Linking.canOpenURL(url);
if (canOpenUrl) {
// Open everything else in the mobile browser
await Linking.openURL(url);
}
}
if (initialUrl) {
handleUniversalLink(initialUrl);
}
// Subscribe to universal links
const subscription = Linking.addEventListener('url', ({url}) => {
handleUniversalLink(url);
});
return () => {
// Remove event listeners on unmount to prevent memory leaks
subscription.remove();
};
}, [initialUrl, shopify, navigation]);
return (
// ... route components ...
);
}
class StorefrontURL {
readonly url: string;
constructor(url: string) {
this.url = url;
}
isThankYouPage(): boolean {
return /thank[-_]you/i.test(this.url);
}
isCheckout(): boolean {
return this.url.includes('/checkout');
}
isCart() {
return this.url.includes('/cart');
}
}
Anchor to Step 4: Test your changesStep 4: Test your changes
This section describes steps you can take to ensure Universal Links and App Links are configured correctly in your app.
Anchor to Validate well-known filesValidate well-known files
Verify the configuration of Universal Links and App Links by using CURL to check the well-known routes on your store:
curl https://example.com/.well-known/apple-app-site-association | jq .
curl https://example.com/.well-known/assetlinks.json | jq .
Anchor to Simulate linking with terminalSimulate linking with terminal
With Universal Links set up, you can test the implementation in the simulator by triggering URLs manually with:
xcrun simctl openurl booted "https://www.example.com/cart"
adb shell am start -a android.intent.action.VIEW -d "https://example.com/cart"
Anchor to iOS diagnosticsi OS diagnostics
Open Settings on a real device and search Universal Links:
Settings > Developer | Universal Links > Diagnostics | Link configured |
---|---|---|
![]() | ![]() | ![]() |
Anchor to Android diagnosticsAndroid diagnostics
Use the App Links Assistant and App Link Testing features in Android Studio to test your app links:

Anchor to Security considerationsSecurity considerations
- Data privacy: Universal Links can pass sensitive information through URLs. Ensure any personal or sensitive data is encrypted or tokenized to protect user privacy.
- URL handling: Rigorously validate and sanitize any data received from Universal Links to prevent URL injection attacks. Only allow expected URL formats and handle unexpected URLs gracefully.
- App update and expiry: Keep your app updated to handle links correctly, and ensure that any links requiring special app capabilities are managed. If a link cannot be handled by the currently installed app, consider providing users with a fallback option or gracefully notifying them.
- Testing and monitoring: Regularly test the functionality of Universal Links to ensure they are correctly pointing to your app and not opening other apps or browsers with potentially harmful content. Monitor your app for any attempts at misuse or abuse of links.
- Certificate management: Maintain secure and appropriate certificate management practices for your app to prevent unauthorized access or configuration changes related to Universal Links.