-
Notifications
You must be signed in to change notification settings - Fork 33
feat: deep link from web app to mobile app for plugin execution #300
Description
Problem
Users visiting demo.tlsnotary.org on their phone cannot generate proofs without the Chrome extension. The mobile app can generate proofs natively, but there's no way for the web app to invoke the mobile app and receive results back.
Proposed Solution
Enable the web app (demo.tlsnotary.org) to detect the TLSNotary mobile app, launch it via deep link to execute a specific plugin, and receive the proof result back — similar to how payment apps handle "pay with app" flows.
Architecture
demo.tlsnotary.org TLSN Mobile App Relay Server
│ │ │
│ 1. Generate correlationId (UUID) │ │
│ │ │
│ 2. POST /register │ │
│ {correlationId, callbackUrl} ──┼──────────────────────────── │
│ │ Store mapping
│ 3. Open deep link: │ │
│ tlsn-mobile://execute? │ │
│ plugin=spotify& │ │
│ correlationId=uuid& │ │
│ verifierUrl=https://...& │ │
│ callbackUrl=https://demo... │ │
│ ──────────────────────────────────→│ │
│ │ │
│ 4. Parse deep link │
│ Load plugin by ID │
│ Show plugin UI │
│ User logs in + proves │
│ │ │
│ 5. Proof complete │
│ POST /result │
│ {correlationId, proof} │
│ │─────────────────────────────→│
│ │ Store result
│ │ │
│ 6. Open callback URL: │
│ https://demo.../callback? │
│ correlationId=uuid& │
│ status=complete │
│←──────────────────────────────────│ │
│ │ │
│ 7. GET /result/:correlationId │ │
│ ──────────────────────────────────┼──────────────────────────────→│
│ │ Return proof
│←──────────────────────────────────┼──────────────────────────────│
│ │ │
│ 8. Display verified result │ │
How the correlationId works
The correlationId is the glue between the web session and the mobile proof, identical to the pattern used in the EAS webhook flow (#266):
- Web app generates a UUID (
correlationId) when the user clicks "Prove on Mobile" - Web app registers the correlationId with a relay server (POST
/register), associating it with a callback URL and optionally the user's session/wallet - Web app opens the deep link with the
correlationIdembedded as a query parameter - Mobile app receives the deep link, extracts the
correlationId, and threads it through the plugin execution assessionData(same as EAS:sessionData: { correlationId }) - Mobile app completes the proof and POSTs the result to the relay server with the
correlationId - Mobile app opens the callback URL in the system browser, returning the user to the web app
- Web app polls or receives the result from the relay server using the
correlationId
The relay server is necessary because:
- Deep link URL params have ~2KB size limits — proof results are larger
- The web app may not be in the foreground when the proof completes
- It decouples the mobile app from needing to know the web app's exact URL structure
- It's the same relay pattern as the EAS webhook (can reuse the same server)
Deep Link Format
Custom URL Scheme
tlsn-mobile://execute?
plugin=spotify&
correlationId=550e8400-e29b-41d4-a716-446655440000&
verifierUrl=https://demo.tlsnotary.org&
relayUrl=https://demo.tlsnotary.org/api&
callbackUrl=https://demo.tlsnotary.org/callback
| Parameter | Required | Description |
|---|---|---|
plugin |
Yes | Plugin ID from the registry (e.g., spotify, swissbank) |
correlationId |
Yes | UUID linking this request to the web session |
verifierUrl |
Yes | Verifier server URL for proof generation |
relayUrl |
Yes | Relay server URL where the mobile app POSTs results |
callbackUrl |
Yes | URL to open after proof completes (returns user to web app) |
Universal Links (iOS) / App Links (Android) — future
For seamless detection without the "Open in app?" prompt:
https://tlsn.app/execute?plugin=spotify&correlationId=...
Requires:
- iOS:
apple-app-site-associationfile on the domain - Android:
assetlinks.jsonon the domain - Both map the URL pattern to the mobile app
App Detection
The web app needs to detect whether the mobile app is installed:
iOS
- Custom scheme:
window.location = 'tlsn-mobile://...'with a timeout fallback. If the app isn't installed, nothing happens — after 1.5s, show "App not installed" with an App Store link - Universal Links (better UX): The OS handles the routing transparently. If the app is installed, it opens. If not, the URL falls through to the web page
Android
- Intent scheme (recommended):
This tries the app first. If not installed, opens the Play Store link automatically.
intent://execute?plugin=spotify&correlationId=...#Intent; scheme=tlsn-mobile; package=org.tlsnotary.mobile; S.browser_fallback_url=https://play.google.com/store/apps/details?id=org.tlsnotary.mobile; end - Custom scheme: Same timeout-based fallback as iOS
Relay Server API
The relay server can be an extension of the existing EAS webhook server or a separate lightweight service.
POST /register
Called by the web app before opening the deep link.
{
"correlationId": "550e8400-e29b-41d4-a716-446655440000",
"callbackUrl": "https://demo.tlsnotary.org/callback",
"metadata": {
"plugin": "spotify",
"userAgent": "...",
"timestamp": 1711900000
}
}POST /result
Called by the mobile app after proof generation.
{
"correlationId": "550e8400-e29b-41d4-a716-446655440000",
"status": "complete",
"proof": {
"results": [{ "value": "Radiohead" }],
"response": {
"status": 200,
"headers": [...],
"body": "..."
}
}
}GET /result/:correlationId
Polled by the web app to check if the mobile app has submitted a result.
{
"status": "complete",
"proof": { ... }
}Status values: pending (registered, waiting for mobile), complete (proof received), expired (TTL exceeded).
Implementation Plan
Phase 1: Deep link handler in mobile app
packages/mobile/:
- Register
tlsn-mobile://scheme inapp.json(already done:"scheme": "tlsn-mobile") - Add deep link handler using
expo-linking(already a dependency) - Parse
plugin,correlationId,verifierUrl,relayUrl,callbackUrlfrom URL params - Navigate to the plugin screen with overridden config (verifierUrl from deep link, not localhost)
- On plugin completion, POST result to
relayUrland opencallbackUrlin system browser
Phase 2: Web app "Prove on Mobile" button
packages/demo/:
- Add mobile detection (check if user is on mobile browser)
- Add "Prove on Mobile" button that generates deep link
- Register correlationId with relay server
- Poll for result or listen for callback navigation
Phase 3: Relay server
packages/eas-webhook/ or new packages/relay/:
/register— store correlationId + metadata/result— receive proof from mobile/result/:id— poll endpoint for web app- In-memory store with TTL (same pattern as EAS webhook)
- Could extend the existing EAS webhook server since the pattern is identical
Phase 4: Universal Links / App Links (optional)
- Host
apple-app-site-associationontlsn.appordemo.tlsnotary.org - Host
assetlinks.jsonfor Android - Eliminates the "Open in app?" prompt and handles app-not-installed gracefully
Integration with EAS Webhook
The relay server pattern is identical to the EAS webhook correlation flow. The same server could handle both:
| Endpoint | EAS Flow | Deep Link Flow |
|---|---|---|
POST /register |
Register wallet + correlationId | Register callbackUrl + correlationId |
| Proof submission | Verifier webhook → /webhook |
Mobile app → POST /result |
GET /attestation/:id |
Poll attestation status | Poll proof result |
The key difference: in the EAS flow, the verifier server POSTs the webhook. In the deep link flow, the mobile app POSTs the result directly. Both use correlationId as the linking key.
Security Considerations
- correlationId should be a cryptographically random UUID (not sequential)
- Relay server should enforce TTL on stored results (e.g., 1 hour) and rate-limit registrations
- Proof results should be verified by the relay server or the web app before displaying (check that the proof came from a trusted verifier)
- callbackUrl should be validated against an allowlist to prevent open redirects
- HTTPS only for all relay server communication