Generate signed URLs server-side to initiate the consent flow. Each link is unique to a user and includes an HMAC signature for security.
URL structure
https://link.emergedata.ai/link/start?client_id=...&redirect_uri=...&state=...×tamp=...&signature=...
Required parameters
| Parameter | Type | Description |
|---|
client_id | string | Your client identifier from the Control Room |
redirect_uri | string | URL to redirect after consent |
state | string | Random string for CSRF protection (you verify this in callback) |
timestamp | string | ISO 8601 timestamp (e.g., 2024-01-15T10:30:00.000Z) |
signature | string | HMAC-SHA256 signature of sorted parameters |
Optional parameters
| Parameter | Type | Default | Description |
|---|
uid | string | - | Your internal user id (server-side only). If omitted, Emerge generates a pairwise uid and returns it on callback. |
flow_config | string | - | Named configuration for custom branding |
Complete implementation
Do not ask end-users for uid or collect it in the frontend. Use your internal user id server-side or omit uid and store the generated callback uid. This prevents wrong-user data access and keeps identifiers private.
import crypto from 'crypto';
interface LinkParams {
clientId: string;
signingSecret: string;
redirectUri: string;
userId?: string;
}
function createLinkUrl(params: LinkParams): string {
const timestamp = new Date().toISOString();
const state = crypto.randomBytes(16).toString('hex');
// Build parameters object
const urlParams: Record<string, string> = {
client_id: params.clientId,
redirect_uri: params.redirectUri,
state: state,
timestamp: timestamp
};
if (params.userId) {
urlParams.uid = params.userId;
}
// Sort and create signature base (raw values, not URL-encoded)
const sortedKeys = Object.keys(urlParams).sort();
const signatureBase = sortedKeys
.map(key => `${key}=${urlParams[key]}`)
.join('&');
// Generate signature
const signature = crypto
.createHmac('sha256', params.signingSecret)
.update(signatureBase)
.digest('hex');
// Build final URL (URLSearchParams handles encoding)
const finalParams = new URLSearchParams(urlParams);
finalParams.append('signature', signature);
return `https://link.emergedata.ai/link/start?${finalParams.toString()}`;
}
// Store state for verification in callback
const stateStore = new Map<string, { userId: string; createdAt: number }>();
function createLinkWithStateTracking(params: LinkParams): {
url: string;
state: string;
} {
const timestamp = new Date().toISOString();
const state = crypto.randomBytes(16).toString('hex');
// Store state for later verification
stateStore.set(state, {
userId: params.userId || '',
createdAt: Date.now()
});
const urlParams: Record<string, string> = {
client_id: params.clientId,
redirect_uri: params.redirectUri,
state: state,
timestamp: timestamp
};
if (params.userId) {
urlParams.uid = params.userId;
}
const sortedKeys = Object.keys(urlParams).sort();
const signatureBase = sortedKeys
.map(key => `${key}=${urlParams[key]}`)
.join('&');
const signature = crypto
.createHmac('sha256', params.signingSecret)
.update(signatureBase)
.digest('hex');
const finalParams = new URLSearchParams(urlParams);
finalParams.append('signature', signature);
return {
url: `https://link.emergedata.ai/link/start?${finalParams.toString()}`,
state: state
};
}
Usage examples
Basic link generation
const url = createLinkUrl({
clientId: process.env.EMERGE_CLIENT_ID!,
signingSecret: process.env.EMERGE_SIGNING_SECRET!,
redirectUri: 'https://yourapp.com/emerge/callback',
userId: 'psub_d4e5f6789012345678901234abcdef01'
});
// Redirect user to this URL or display as a button
console.log(url);
Express.js endpoint
import express from 'express';
const app = express();
app.get('/connect-data', (req, res) => {
const userId = req.session.userId;
const { url, state } = createLinkWithStateTracking({
clientId: process.env.EMERGE_CLIENT_ID!,
signingSecret: process.env.EMERGE_SIGNING_SECRET!,
redirectUri: 'https://yourapp.com/emerge/callback',
userId: userId
});
// Store state in session for verification
req.session.emergeState = state;
res.redirect(url);
});
FastAPI endpoint
from fastapi import FastAPI, Request
from fastapi.responses import RedirectResponse
import os
app = FastAPI()
@app.get('/connect-data')
async def connect_data(request: Request):
user_id = request.session.get('user_id')
url, state = create_link_with_state_tracking(LinkParams(
client_id=os.environ['EMERGE_CLIENT_ID'],
signing_secret=os.environ['EMERGE_SIGNING_SECRET'],
redirect_uri='https://yourapp.com/emerge/callback',
user_id=user_id
))
# Store state in session for verification
request.session['emerge_state'] = state
return RedirectResponse(url)
Link validity
- Links are valid for 30 days from the timestamp
- The same link parameters generate the same session (idempotent)
- Expired links show an error page to users
Generate links on-demand rather than storing them. This ensures the timestamp is always fresh.
Signature algorithm
The signature ensures the link hasn’t been tampered with:
- Collect all URL parameters (except
signature)
- Sort parameters alphabetically by key
- Join as
key=value&key=value (raw values, not URL-encoded)
- Generate HMAC-SHA256 using your signing secret
- Append signature as hex string
The signature base uses raw parameter values, not URL-encoded values. URL encoding is only applied when constructing the final URL.