---
title: "Install Links & Deep Links | Tajo"
description: "Generate install URLs for app distribution and deep links to navigate users to specific app views"
url: https://tajo.io/docs/stripe-apps/install-links/
markdown: https://tajo.io/docs/stripe-apps/install-links.md
lang: en-US
author: "Set Noa"
author_url: https://tajo.io/set-noa/
---

# Install Links & Deep Links

[Set Noa](https://tajo.io/set-noa/)

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.

Caution

**Status: planned, not built.** Tajo has no Stripe App, so nothing in this section can be installed today. The app identifiers, URLs and credentials on this page are illustrative placeholders, not live endpoints.

## 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:

```json
{
  "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_xxxxx
```

### CSRF Protection

Always use the `state` parameter to prevent cross-site request forgery attacks:

```javascript
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 = async (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:

```javascript
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:

```bash
# 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

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

### Generating Deep Links Programmatically

```javascript
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',
});
```

### 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”

```html
<!-- 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:

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

```javascript
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.
