Skip to main content

Web/React SDK Integration Guide

This guide provides comprehensive instructions for integrating the Passage Web/React SDK into your React application and implementing the required backend infrastructure.

Table of Contents

  1. Installation
  2. SDK Integration
  3. Backend Implementation
  4. API Reference

Installation

Requirements

  • React 16.8+
  • TypeScript 4.0+ (recommended)
  • Node.js 16+

NPM Installation

Add the package to your React project:

npm install @getpassage/react-js

Yarn Installation

yarn add @getpassage/react-js

SDK Integration

Overview

The Passage Web/React SDK follows these steps:

  1. Configure SDK → Wrap your app with PassageProvider
  2. Get Token → Get intent token from your backend
  3. Open Passage → Launch connection flow with callbacks
  4. Handle Results → Process success/error responses

Quick Reference

import { PassageProvider, usePassage, PassageSuccessData, PassageErrorData } from '@getpassage/react-js';

function App() {
return (
<PassageProvider config={{}}>
<MyComponent />
</PassageProvider>
);
}

function MyComponent() {
const { open } = usePassage();

const handleConnect = async () => {
// 1. Get intent token from your backend
const response = await fetch('https://your-api.com/api/intent-token');
const { intentToken } = await response.json();

// 2. Open Passage with the token
await open({
token: intentToken,
onConnectionComplete: (data: PassageSuccessData) => {
console.log('Success:', data.connectionId);
console.log('Return URL:', data.returnUrl);
},
onConnectionError: (error: PassageErrorData) => {
console.log('Error:', error.error);
}
});
};

return <button onClick={handleConnect}>Connect</button>;
}

Detailed Implementation

import React, { useState } from 'react';
import {
PassageProvider,
usePassage,
PassageSuccessData,
PassageErrorData
} from '@getpassage/react-js';

function App() {
return (
<PassageProvider config={{}}>
<ConnectAccount />
</PassageProvider>
);
}

function ConnectAccount() {
const { open } = usePassage();
const [isLoading, setIsLoading] = useState(false);
const [result, setResult] = useState('');

const connectAccount = async () => {
setIsLoading(true);

try {
// 1. Get intent token from your backend
const response = await fetch('https://your-api.com/api/intent-token');
const { intentToken } = await response.json();

// 2. Open Passage with the token
await open({
token: intentToken,
onConnectionComplete: (data: PassageSuccessData) => {
setResult(`✅ Connected! ID: ${data.connectionId}`);
},
onConnectionError: (error: PassageErrorData) => {
setResult(`❌ Error: ${error.error}`);
},
onExit: (reason) => {
console.log('User exited:', reason);
}
});

} catch (error) {
setResult(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
} finally {
setIsLoading(false);
}
};

return (
<div style={{ padding: '20px' }}>
<h1>My App</h1>
<button
onClick={connectAccount}
disabled={isLoading}
style={{
backgroundColor: isLoading ? '#ccc' : '#007AFF',
color: 'white',
padding: '15px 30px',
borderRadius: '8px',
border: 'none',
fontSize: '16px',
cursor: isLoading ? 'not-allowed' : 'pointer'
}}
>
{isLoading ? 'Connecting...' : 'Connect Account'}
</button>

{result && (
<p style={{ marginTop: '20px', fontSize: '14px' }}>
{result}
</p>
)}
</div>
);
}

export default App;

Backend Implementation

Your backend needs to create intent tokens for the React app to use. Here's a typical implementation:

Node.js Example

import express from 'express';
import fetch from 'node-fetch';

const PASSAGE_CLIENT_ID = process.env.PASSAGE_CLIENT_ID!;
const PASSAGE_CLIENT_SECRET = process.env.PASSAGE_CLIENT_SECRET!;

// Intent token payload interfaces
interface BaseIntentTokenPayload {
integrationId: string;
resources: Record<string, any>;
}

interface IntentTokenResponse {
intentToken: string;
connectionId: string;
url: string;
}

// Helper to get OAuth access token
async function getPassageAccessToken(): Promise<string> {
const response = await fetch('https://api.runpassage.ai/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: PASSAGE_CLIENT_ID,
client_secret: PASSAGE_CLIENT_SECRET
})
});

if (!response.ok) {
const error = await response.text();
throw new Error(`Failed to get access token: ${error}`);
}

const { access_token } = await response.json();
return access_token;
}

// Helper to call Passage to create an intent token
async function createIntentToken(
payload: BaseIntentTokenPayload
): Promise<IntentTokenResponse> {
const accessToken = await getPassageAccessToken();

const res = await fetch('https://api.runpassage.ai/intent-token', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});

if (!res.ok) {
const text = await res.text();
throw new Error(`Passage intent token error ${res.status}: ${text}`);
}

return res.json();
}

const app = express();
app.use(express.json());

app.post('/api/intent-token', async (req, res) => {
try {
const payload: BaseIntentTokenPayload = {
integrationId: 'airbnb',
resources: {
trip: {
read: {}
}
}
};

const token = await createIntentToken(payload);

res.json({ intentToken: token.intentToken });

} catch (error) {
res.status(500).json({
error: error instanceof Error ? error.message : 'Unknown error'
});
}
});

app.listen(3000, () => console.log('Backend running on port 3000'));

API Reference

Configuration

import { PassageProvider, PassageConfig } from '@getpassage/react-js';

interface PassageConfig {
publishableKey?: string; // Optional - only required for app clip flow
uiUrl?: string; // UI URL (default: https://ui.runpassage.ai)
apiUrl?: string; // API URL (default: https://api.runpassage.ai)
socketUrl?: string; // Socket URL (default: https://api.runpassage.ai)
socketNamespace?: string; // Socket namespace (default: /ws)
}

// Wrap your app with PassageProvider
function App() {
return (
<PassageProvider config={{}}>
<YourApp />
</PassageProvider>
);
}

React Hook

import { usePassage } from '@getpassage/react-js';

const { open, close } = usePassage();

// Open connection modal with token
await open(options: PassageOpenOptions): Promise<void>;

// Close connection programmatically
close(): void;

Data Types

PassageOpenOptions

interface PassageOpenOptions {
token: string; // Required: intent token
onConnectionComplete?: (data: PassageSuccessData) => void;
onConnectionError?: (error: PassageErrorData) => void;
onExit?: (reason?: string) => void;
}

PassageSuccessData

interface PassageSuccessData {
connectionId: string; // Unique connection identifier
status: string; // Connection status
metadata?: {
completedAt: string; // Completion timestamp
};
data?: any; // Result data
intentToken?: string; // Intent token used
returnUrl?: string; // Return URL if configured
}

📚 For detailed data structures returned by each integration, see Integration Data Types →

PassageErrorData

interface PassageErrorData {
error: string; // Error message
code?: string; // Error code
data?: any; // Additional error data
}

Integration Data Types

  • Browse integrations and resources in the explorer

Support

If you encounter issues: