딥링크는 앱 내부의 특정 경로로 사용자를 랜딩시키는 기능이다. 알림톡, 푸시 알림은 물론이고 특정 URL 링크를 누르면 웹페이지 대신 앱이 열리고 특정 화면으로 이동하는 것이다. 웹에서는 알아서 브라우저가 이동시켜주지만, 모바일 환경에서는 딥링크를 활용해 화면으로 직접 랜딩해주어야 한다.
안드로이드 위주로 작업했기 때문에, 안드로이드 위주로 작성한다.
딥링크의 세 가지 동작 방식
딥링크는 구현 방식과 앱 설치 상태에 따라 크게 세 가지 종류로 나뉜다.
Direct Deep Link (Custom URL Scheme)
앱 고유의 커스텀 스킴(myapp://path)을 정의해 앱을 직접 실행한다. 앱이 이미 설치되어 있을 때 가장 빠르게 특정 화면으로 이동할 수 있지만, 도메인 검증이 없어 다른 앱과 스킴 이름이 충돌할 수 있다. 예를 들어 네이버 지도 앱의 nmap:// 같은 형태다.
Verified Deep Link (Android App Links / iOS Universal Links) 표준 웹 URL을 식별자로 사용한다. 앱이 설치되어 있으면 앱으로 이동하고 미설치 상태이면 웹페이지로 랜딩된다. OS 차원에서 도메인 소유권을 검증하므로 보안성이 높고 스킴 충돌도 없다.
Deferred Deep Link (지연된 딥링크) 유저가 링크를 눌렀을 때 앱이 설치되어 있지 않더라도, 앱스토어 설치 후 첫 실행 시점에 기존 링크에 담긴 특정 화면 경로와 파라미터를 복원해 연결해 주는 방식이다. OS 수준에서 기본 제공하지 않아서 설치 매개변수를 추적하는 MMP(Branch, AppsFlyer 등)나 클립보드, 서버 매칭을 활용해 구현한다.
안드로이드와 iOS 딥링크 구조 비교
안드로이드와 iOS는 표준 웹 도메인을 검증형 딥링크로 연동할 때 몇 가지 차이가 있다. 부르는 이름도 별도이다.
| 구분 | 안드로이드 (Android App Links) | iOS (Universal Links) |
|---|---|---|
| 검증 파일 | .well-known/assetlinks.json | .well-known/apple-app-site-association (AASA) |
| 파일 형식 | JSON | JSON (확장자 없음, application/json Content-Type) |
| 앱 설정 | AndroidManifest.xml (autoVerify="true") | Xcode Associated Domains (applinks:domain.com) |
| 스킴 충돌 시 | URL Scheme 중복 시 "다음 앱으로 열기" 선택창 노출 | Universal Links 등록 앱이 우선 동작 (선택창 없음) |
| 브라우저 동작 | Chrome 주소창 직접 입력 시에도 App Link 동작 가능 | Safari 주소창 직접 입력 시 미동작 (외부 링크 클릭 시 동작) |
Custom URL Scheme
전통적인 URL 스킴 방식은 sample-app://처럼 앱 고유 스킴을 등록한다. 안드로이드는 AndroidManifest.xml의 intent-filter에 스킴과 호스트를 정의한다.
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sample-app" android:host="app.example.com" />
</intent-filter>
</activity>
sample-app://app.example.com?param=asdf 형태의 링크를 누르면 해당 액티비티가 호출된다. 이때 android:scheme 뿐만 아니라 android:host 속성값까지 URL과 정확히 일치해야 대상 앱을 찾아 실행한다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val data: Uri? = intent.data
val param = data?.getQueryParameter("param") // "asdf"
}
호출된 액티비티에서는 intent.data로 전체 URL을 전달받고, getQueryParameter() 메서드로 파라미터를 추출한다.

iOS 역시 프로젝트 Target의 Info 탭에서 URL Types 항목을 추가하여 앱의 커스텀 스킴을 등록한다.
URL Scheme 스킴 충돌과 선택창 팝업
구글 플레이, 원스토어, 갤럭시 스토어 같은 앱 마켓들은 스킴 이름으로 market://을 공통으로 사용한다. 앱 수가 늘어나며 범용 스킴이 중복되는 문제가 생긴다.

동일한 스킴을 가진 여러 앱이 설치되어 있으면, 링크를 누르는 순간 안드로이드 OS는 다음 앱으로 열기 선택창을 팝업으로 노출하여 유저에게 앱을 직접 선택하도록 유도한다. 이 방식은 사용자 입장에서는 선택지를 주지만, 귀찮음도 함께 줄 수 있다.
Android App Links 도메인 검증 및 SPA 배포 트러블슈팅
스킴 중복 충돌을 방지하기 위해 표준 HTTP/HTTPS 웹 도메인을 식별자로 사용하는 Android App Links를 적용한다.
<activity android:name=".MainActivity" android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="app.example.com" />
</intent-filter>
</activity>
<intent-filter>에 android:autoVerify="true" 속성을 추가하고 https 스킴과 서비스 도메인을 등록한다.
assetlinks.json
안드로이드 시스템이 앱링크 도메인 소유권을 검증하려면 웹 서버의 https://app.example.com/.well-known/assetlinks.json 경로로 서명 정보를 제공해야 한다. 마치 검색 엔진에 소유권 검사하듯이.
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.myapp",
"sha256_cert_fingerprints": [
"11:22:33:44:55:66:77:88:99:00:AA:BB:CC:DD:EE:FF:11:22:33:44:55:66:77:88:99:00:AA:BB:CC:DD:EE:FF"
]
}
}]
namespace 를 반드시 "android_app"으로 설정해야 앱이 정상 연동된다. 위 파일을 빌드, 배포할 때 맞는 경로로 배포되게끔 하면 된다.
SHA-256 지문 추출 방법
sha256_cert_fingerprints에 들어갈 서명 지문은 Android Studio 터미널에서 Gradle 명령어로 추출한다.
./gradlew signingReport

Gradle 실행 결과로 출력된 SHA256 해시 키값을 복사해 assetlinks.json 에 붙여넣는다. 디버그 인증서($HOME/.android/debug.keystore)와 프로덕션 서명 키 지문을 모두 등록해 두어야 상황에 맞게 딥링크를 검증할 수 있다.
iOS Universal Links와 AASA 파일 설정

iOS 환경에서는 Universal Links로 도메인 기반 딥링크를 구성한다. Associated Domains에 applinks:app.example.com 을 선언한다. 웹 서버의 https://app.example.com/.well-known/apple-app-site-association 경로에 AASA 파일을 배치한다.
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.example.myapp",
"paths": ["/items/*", "/notice/*"]
}
]
}
}
파일명에 .json 확장자를 붙이지 않고 순수 JSON 내용으로 구성해야 한다. 응답 헤더 Content-Type은 application/json 이어야 정상 작동한다.
카카오톡 / 알림톡 내 App Links
카카오톡이나 서드파티 앱의 인앱 브라우저 내부에서는 보안 및 브라우저 세션 정책으로 인해 링크를 눌러도 앱으로 직행하지 않고 무조건 인앱의 웹뷰로 페이지가 열리는 현상이 있다. 카카오 알림톡이나 템플릿 메시지를 발송할 때는 딥링크 단독 사용보다 웹 링크와 조합해서 사용해야 한다.
graph TD
A[유저 알림톡/메시지 링크 클릭] --> B{진입 환경 및 앱 설치 확인}
B -- 카카오톡 인앱 브라우저 진입 --> C[Mobile / PC Web URL 랜딩]
C --> D[웹페이지 내 스토어 이동 또는 커스텀 스킴 버튼 노출]
B -- 외부 브라우저 & 앱 설치됨 --> E[Direct / Verified 딥링크로 앱 특정 화면 이동]
B -- 앱 미설치 유저 --> F[모바일 웹 랜딩 후 스토어 설치 안내]
알림톡 메시지 템플릿 구성 시 Android URL, iOS URL에는 딥링크 스킴을 지정하고, Mobile URL, PC URL에는 웹 랜딩 주소를 각각 분기 지정하여 딥링크 미지원 환경이나 인앱 브라우저 진입 시에도 안전하게 유저를 랜딩시키는 방식이 안정적이다.