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 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"
]
}
https://marketplace.stripe.com/oauth/v2/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&state=STATE_VALUE
ParameterRequiredDescription
client_idYesYour app ID (e.g., com.tajo.brevo-integration)
redirect_uriYesMust match one of your allowed_redirect_uris
stateRecommendedRandom string for CSRF protection

Redirect Parameters

After a successful installation, Stripe redirects the user to your redirect_uri with these query parameters:

ParameterDescription
user_idThe Stripe user ID of the installing account
account_idThe Stripe account ID (e.g., acct_xxxxx)
stateThe state value you provided (for CSRF verification)
install_signatureHMAC 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_xxxxx

CSRF 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 session
const 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 callback
const 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:

Terminal window
# Set as environment variable
export STRIPE_APP_SIGNING_SECRET="whsec_xxxxx"

Never hardcode signing secrets in your source code or commit them to version control.

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.

https://dashboard.stripe.com/MODE/acct_ID/PAGE?apps[APP_ID][TARGET]=VIEWPORT_ID
ComponentDescriptionExample
MODElive or testlive
acct_IDTarget Stripe account IDacct_1234567890
PAGEDashboard page pathcustomers/cus_xxxxx
APP_IDYour app’s IDcom.tajo.brevo-integration
TARGETdrawer or modaldrawer
VIEWPORT_IDThe viewport to openstripe.dashboard.customer.detail

Drawer vs Modal Targets

TargetBehaviorUse Case
drawerOpens the app in the side panel (drawer)Default app interaction, context alongside the page
modalOpens the app in a full-screen modal overlayFocused workflows, onboarding, complex forms

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.detail

Open settings in modal

https://dashboard.stripe.com/live/acct_xxxxx/settings
?apps[com.tajo.brevo-integration][modal]=stripe.dashboard.settings

Open onboarding flow

https://dashboard.stripe.com/live/acct_xxxxx/dashboard
?apps[com.tajo.brevo-integration][modal]=stripe.dashboard.onboarding

Open 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.detail
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 profile
const customerLink = generateDeepLink({
accountId: 'acct_xxxxx',
page: 'customers/cus_xxxxx',
viewport: 'stripe.dashboard.customer.detail',
});
// Generate a link to app settings
const settingsLink = generateDeepLink({
accountId: 'acct_xxxxx',
page: 'settings',
viewport: 'stripe.dashboard.settings',
target: 'modal',
});

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>

For the best onboarding experience, combine install links with post-install deep links:

  1. User clicks an install link from your website or email
  2. User installs the app and is redirected to your callback URL
  3. 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.

AI Assistant

Hi! Ask me anything about the docs.

Start Free with Brevo