Install Links & Deep Links
Install links let you distribute your Stripe App outside the marketplace, while deep links navigate users directly to specific views within your installed app. Both are essential for smooth onboarding and integration flows.
Install Links
Install links provide a direct URL that merchants can use to install your app. When a user clicks an install link, Stripe handles the installation flow and then redirects back to your specified URI.
Prerequisites
Before using install links, configure allowed_redirect_uris in your app manifest:
{ "id": "com.tajo.brevo-integration", "allowed_redirect_uris": [ "https://tajo.io/stripe/callback", "https://tajo.io/stripe/oauth/complete" ]}Install Link Format
https://marketplace.stripe.com/oauth/v2/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&state=STATE_VALUE| Parameter | Required | Description |
|---|---|---|
client_id | Yes | Your app ID (e.g., com.tajo.brevo-integration) |
redirect_uri | Yes | Must match one of your allowed_redirect_uris |
state | Recommended | Random string for CSRF protection |
Redirect Parameters
After a successful installation, Stripe redirects the user to your redirect_uri with these query parameters:
| Parameter | Description |
|---|---|
user_id | The Stripe user ID of the installing account |
account_id | The Stripe account ID (e.g., acct_xxxxx) |
state | The state value you provided (for CSRF verification) |
install_signature | HMAC signature to verify the install is legitimate |
Example redirect URL:
https://tajo.io/stripe/callback ?user_id=usr_xxxxx &account_id=acct_xxxxx &state=abc123random &install_signature=sig_xxxxxCSRF Protection
Always use the state parameter to prevent cross-site request forgery attacks:
import crypto from 'crypto';
// Generate a random state value and store it in the sessionconst generateInstallLink = (req, res) => { const state = crypto.randomBytes(32).toString('hex');
// Store state in session for later verification req.session.stripeInstallState = state;
const installUrl = new URL('https://marketplace.stripe.com/oauth/v2/authorize'); installUrl.searchParams.set('client_id', 'com.tajo.brevo-integration'); installUrl.searchParams.set('redirect_uri', 'https://tajo.io/stripe/callback'); installUrl.searchParams.set('state', state);
res.redirect(installUrl.toString());};
// Handle the redirect callbackconst handleInstallCallback = (req, res) => { const { state, user_id, account_id, install_signature } = req.query;
// Verify state matches what we stored if (state !== req.session.stripeInstallState) { return res.status(403).json({ error: 'Invalid state parameter' }); }
// Clear the stored state delete req.session.stripeInstallState;
// Verify the install signature if (!verifyInstallSignature(install_signature, account_id)) { return res.status(403).json({ error: 'Invalid install signature' }); }
// Process the successful installation await processInstallation(user_id, account_id);
res.redirect('/dashboard/stripe-connected');};Signature Verification
Verify the install_signature using your app’s signing secret:
import crypto from 'crypto';
const verifyInstallSignature = (signature, accountId) => { const signingSecret = process.env.STRIPE_APP_SIGNING_SECRET;
const expectedSignature = crypto .createHmac('sha256', signingSecret) .update(accountId) .digest('hex');
return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) );};Caution
Always use crypto.timingSafeEqual for signature comparison to prevent timing attacks. Never use simple string equality (===).
Signing Secret
Your app’s signing secret is available in the Stripe Dashboard under your app’s settings. Use it to:
- Verify install signatures from redirect callbacks
- Validate webhook payloads from Stripe
- Authenticate requests between your backend and Stripe
Store the signing secret securely:
# Set as environment variableexport STRIPE_APP_SIGNING_SECRET="whsec_xxxxx"Never hardcode signing secrets in your source code or commit them to version control.
Deep Links
Deep links navigate users directly to a specific view within your installed Stripe App. Use them to direct users from external communications (emails, notifications, support pages) to the relevant app context.
Deep Link URL Format
https://dashboard.stripe.com/MODE/acct_ID/PAGE?apps[APP_ID][TARGET]=VIEWPORT_ID| Component | Description | Example |
|---|---|---|
MODE | live or test | live |
acct_ID | Target Stripe account ID | acct_1234567890 |
PAGE | Dashboard page path | customers/cus_xxxxx |
APP_ID | Your app’s ID | com.tajo.brevo-integration |
TARGET | drawer or modal | drawer |
VIEWPORT_ID | The viewport to open | stripe.dashboard.customer.detail |
Drawer vs Modal Targets
| Target | Behavior | Use Case |
|---|---|---|
drawer | Opens the app in the side panel (drawer) | Default app interaction, context alongside the page |
modal | Opens the app in a full-screen modal overlay | Focused workflows, onboarding, complex forms |
Deep Link Examples
Open customer detail view in drawer
https://dashboard.stripe.com/live/acct_xxxxx/customers/cus_xxxxx ?apps[com.tajo.brevo-integration][drawer]=stripe.dashboard.customer.detailOpen settings in modal
https://dashboard.stripe.com/live/acct_xxxxx/settings ?apps[com.tajo.brevo-integration][modal]=stripe.dashboard.settingsOpen onboarding flow
https://dashboard.stripe.com/live/acct_xxxxx/dashboard ?apps[com.tajo.brevo-integration][modal]=stripe.dashboard.onboardingOpen payment detail view in test mode
https://dashboard.stripe.com/test/acct_xxxxx/payments/pi_xxxxx ?apps[com.tajo.brevo-integration][drawer]=stripe.dashboard.payment.detailGenerating Deep Links Programmatically
const generateDeepLink = ({ accountId, mode = 'live', page, appId = 'com.tajo.brevo-integration', target = 'drawer', viewport,}) => { const baseUrl = `https://dashboard.stripe.com/${mode}/${accountId}/${page}`; const params = new URLSearchParams(); params.set(`apps[${appId}][${target}]`, viewport);
return `${baseUrl}?${params.toString()}`;};
// Generate a link to view a customer's Brevo profileconst customerLink = generateDeepLink({ accountId: 'acct_xxxxx', page: 'customers/cus_xxxxx', viewport: 'stripe.dashboard.customer.detail',});
// Generate a link to app settingsconst settingsLink = generateDeepLink({ accountId: 'acct_xxxxx', page: 'settings', viewport: 'stripe.dashboard.settings', target: 'modal',});Using Deep Links in Communications
Deep links are particularly useful in:
- Email notifications: “View Brevo sync status for this customer”
- Support responses: “Click here to check your integration settings”
- Onboarding emails: “Complete your Brevo setup”
- Error alerts: “Review the sync issue for customer X”
<!-- Example in an email template --><a href="https://dashboard.stripe.com/live/acct_xxxxx/customers/cus_xxxxx?apps[com.tajo.brevo-integration][drawer]=stripe.dashboard.customer.detail"> View Brevo Profile in Stripe</a>Combining Install Links and Deep Links
For the best onboarding experience, combine install links with post-install deep links:
- User clicks an install link from your website or email
- User installs the app and is redirected to your callback URL
- Your callback processes the installation and redirects the user to a deep link that opens the onboarding viewport
const handleInstallCallback = async (req, res) => { const { account_id, install_signature, state } = req.query;
// Verify state and signature // ... (verification code)
// Process installation await processInstallation(account_id);
// Redirect to the app's onboarding view via deep link const onboardingLink = generateDeepLink({ accountId: account_id, page: 'dashboard', viewport: 'stripe.dashboard.onboarding', target: 'modal', });
res.redirect(onboardingLink);};Tip
Always test install links and deep links in both live and test mode to ensure they work correctly in all environments.