Skip to main content

Client-Side Integration (React)

React SDK Documentation · Version 1.5 · October 2026

Integrate Mubeen Device Intelligence into your React single-page application using the useDeviceId hook. Two integration methods are supported — pick the one that matches your setup.

Overview​

The SDK loads via script tags in index.html, and a custom React hook provides an idiomatic way to access the device ID in your components.

  • useDeviceId Hook — Returns deviceId, loading, and error states
  • Two initialization styles — Manual (explicit init() from a bootstrap module) or Auto (initializes on script load)
  • Non-blocking — Scripts load with defer, hook handles async readiness

Prerequisites​

RequirementDetails
Site KeyStarts with public_ — obtain from your Mubeen dashboard
Backend URLRegion-specific collection endpoint — https://fp.de.mubeen.ai (EU) or https://fp.sa.mubeen.ai (KSA). Required on every initialization.
React16.8+ (requires hooks support)
Browser SupportModern browsers with JavaScript enabled (Chrome, Firefox, Safari, Edge)

Choose Your Integration Method​

The SDK supports two React integration styles. Pick one and use it consistently.

ManualAuto
Scriptmub_fp.jsmub_fp.js?site_key=...&backend_url=...
InitializationExplicit mub_fp.init() in a bootstrap moduleAutomatic on script load
CredentialsPassed in the init() config object from JSPassed as URL query parameters in HTML
Ready patternHook awaits the init promiseHook awaits mub_fp.ready()
Best forDynamic config (e.g. site key fetched at runtime), consent-gated initStatic configuration, quick setup
Which should I choose?

Use Auto if your site_key and region are known at build time — just edit index.html and go. Use Manual if you need to defer initialization (e.g. until after consent) or configure the SDK from JavaScript rather than HTML.


Quick Start​

1. Add the SDK to index.html​

Manual

index.html
<div id="root"></div>

<!--
Load the SDK with no query parameters. Credentials are passed
to mub_fp.init() from the bootstrap module in Step 2.
-->
<script defer src="https://cdn.mubeen.ai/fp/mub_fp.js"></script>

<script type="module" src="/src/main.jsx"></script>

Both <script defer> and <script type="module"> are deferred and execute in document order, so window.mub_fp is defined by the time your app code runs.

Auto

index.html
<div id="root"></div>

<!--
Shim: exposes mub_fp.ready() immediately so the hook can await
it even before the real SDK script has finished downloading.
-->
<script>
(function(){
var r, p = new Promise(function(res){ r = res; });
window.mub_fp = { ready: function(){ return p; }, _readyResolve: r };
})();
</script>
<script defer src="https://cdn.mubeen.ai/fp/mub_fp.js?site_key=public_your_site_key_here&backend_url=https://fp.sa.mubeen.ai"></script>

<script type="module" src="/src/main.jsx"></script>

The shim resolves its promise when the SDK lands. Credentials are supplied via query string — no JavaScript init call needed.

2. Create the useDeviceId Hook​

Manual

First, a tiny bootstrap module that kicks off init() once and exports the promise. Importing this module from main.jsx triggers initialization before React renders; the hook awaits the same promise.

src/mubFp.js
// Fires exactly once — the module is evaluated once, the promise is cached.
// Swap in a runtime-computed site_key / backend_url here if needed.
export const fingerprintReady = window.mub_fp.init({
site_key: 'public_your_site_key_here',
backend_url: 'https://fp.sa.mubeen.ai' // 'https://fp.de.mubeen.ai' for EU data residency
});
src/useDeviceId.js
import { useState, useEffect } from 'react';
import { fingerprintReady } from './mubFp';

export function useDeviceId() {
const [deviceId, setDeviceId] = useState('');
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
// Guards against setting state on an unmounted component if
// the SDK resolves after the component has been torn down.
let cancelled = false;

fingerprintReady
.then(() => window.mub_fp.getDeviceId())
.then((id) => {
if (!cancelled) {
setDeviceId(id);
setLoading(false);
}
})
.catch((err) => {
if (!cancelled) {
setError(err.message || 'Failed to get device ID');
setLoading(false);
}
});

return () => { cancelled = true; };
}, []);

return { deviceId, loading, error };
}

Auto

No bootstrap module — the SDK self-initializes from the query parameters in index.html. The hook just awaits mub_fp.ready().

src/useDeviceId.js
import { useState, useEffect } from 'react';

export function useDeviceId() {
const [deviceId, setDeviceId] = useState('');
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
// Guards against setting state on an unmounted component if
// the SDK resolves after the component has been torn down.
let cancelled = false;

window.mub_fp
.ready()
.then(() => window.mub_fp.getDeviceId())
.then((id) => {
if (!cancelled) {
setDeviceId(id);
setLoading(false);
}
})
.catch((err) => {
if (!cancelled) {
setError(err.message || 'Failed to get device ID');
setLoading(false);
}
});

return () => { cancelled = true; };
}, []);

return { deviceId, loading, error };
}

3. Use the Hook in a Component​

The hook's API is identical for both methods — swap the implementation and your components stay the same.

import { useDeviceId } from './useDeviceId';

function MyComponent() {
const { deviceId, loading, error } = useDeviceId();

if (loading) return <p>Loading device ID...</p>;
if (error) return <p>Error: {error}</p>;

return <p>Device ID: {deviceId}</p>;
}

Form Integration​

In React, there's no need for hidden form fields. Use the useDeviceId hook and include the device ID directly in your fetch request body. The form code is the same for both methods — the only thing that changes is the hook's implementation (Step 2 above).

LoginForm.jsx
import { useState } from 'react';
import { useDeviceId } from './useDeviceId';

export default function LoginForm() {
const { deviceId, loading, error: fpError } = useDeviceId();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleSubmit = async (e) => {
e.preventDefault();
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password, device_id: deviceId }),
});
const data = await res.json();
console.log('Response:', data);
};

return (
<form onSubmit={handleSubmit}>
<input type="email" value={email}
onChange={(e) => setEmail(e.target.value)} placeholder="Email" required />
<input type="password" value={password}
onChange={(e) => setPassword(e.target.value)} placeholder="Password" required />
<button type="submit" disabled={loading}>
{loading ? 'Loading...' : 'Log In'}
</button>
{fpError && <p>Fingerprint error: {fpError}</p>}
</form>
);
}
info

See Server-Side Integration for the expected request format your backend should accept.


Complete Example​

Two end-to-end React SPAs — one for each integration method. Both produce the same component tree; only the bootstrap files (index.html + optionally src/mubFp.js) and the hook implementation differ.

Manual

Five files: the HTML entry, a bootstrap module that runs mub_fp.init(), the useDeviceId hook, the login component, and the React entry point that mounts the app.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login — React SPA</title>
</head>
<body>
<div id="root"></div>

<!--
Plain SDK load — no query parameters, no shim. Credentials are
passed to mub_fp.init() from src/mubFp.js below.
-->
<script defer src="https://cdn.mubeen.ai/fp/mub_fp.js"></script>

<script type="module" src="/src/main.jsx"></script>
</body>
</html>
src/mubFp.js
// Module-level init: evaluated once, the promise is shared by every
// consumer (the hook). This is where you'd read a runtime-fetched
// site_key or a region chosen by the user.
export const fingerprintReady = window.mub_fp.init({
site_key: 'public_your_site_key_here',
backend_url: 'https://fp.sa.mubeen.ai' // 'https://fp.de.mubeen.ai' for EU data residency
});
src/useDeviceId.js
import { useState, useEffect } from 'react';
import { fingerprintReady } from './mubFp';

export function useDeviceId() {
const [deviceId, setDeviceId] = useState('');
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
// Prevents late SDK resolutions from touching state after unmount.
let cancelled = false;

fingerprintReady
.then(() => window.mub_fp.getDeviceId())
.then((id) => {
if (!cancelled) {
setDeviceId(id);
setLoading(false);
}
})
.catch((err) => {
// Fail soft — the UI surfaces the error but the app keeps working.
if (!cancelled) {
setError(err.message || 'Failed to get device ID');
setLoading(false);
}
});

return () => { cancelled = true; };
}, []);

return { deviceId, loading, error };
}
src/App.jsx
import { useState } from 'react';
import { useDeviceId } from './useDeviceId';

export default function App() {
const { deviceId, loading, error: fpError } = useDeviceId();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [submitting, setSubmitting] = useState(false);
const [result, setResult] = useState(null);

const handleSubmit = async (e) => {
e.preventDefault();
setSubmitting(true);
try {
// device_id may be '' if fingerprinting failed — server should
// treat a missing device ID as a risk signal, not a hard block.
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password, device_id: deviceId }),
});
const data = await res.json();
setResult(data);
} catch (err) {
setResult({ error: err.message });
} finally {
setSubmitting(false);
}
};

return (
<div>
<h2>Log In</h2>
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input type="email" id="email" placeholder="[email protected]"
value={email} onChange={(e) => setEmail(e.target.value)} required />

<label htmlFor="password">Password</label>
<input type="password" id="password" placeholder="Enter password"
value={password} onChange={(e) => setPassword(e.target.value)} required />

<button type="submit" disabled={submitting}>
{submitting ? 'Logging in...' : 'Log In'}
</button>
</form>

<div>
<strong>Device ID: </strong>
{fpError ? <span>Error: {fpError}</span>
: loading ? <span>Initializing...</span>
: <span>{deviceId}</span>}
</div>

{result && <pre>{JSON.stringify(result, null, 2)}</pre>}
</div>
);
}
src/main.jsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

// Importing App pulls in useDeviceId, which imports ./mubFp — so
// mub_fp.init() runs here, before the first render.
createRoot(document.getElementById('root')).render(<App />);

Auto

Four files: the HTML entry (shim + query-string init), the useDeviceId hook, the login component, and the React entry point that mounts the app. No bootstrap module — the SDK self-initializes.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login — React SPA</title>
</head>
<body>
<div id="root"></div>

<!--
Shim: gives the page a mub_fp.ready() promise immediately so the
hook can await it before the real SDK script has arrived.
-->
<script>
(function(){
var r, p = new Promise(function(res){ r = res; });
window.mub_fp = { ready: function(){ return p; }, _readyResolve: r };
})();
</script>

<!--
Auto-init: credentials live in the query string. The SDK reads
them on load, initializes itself, then resolves the shim's promise.
-->
<script defer src="https://cdn.mubeen.ai/fp/mub_fp.js?site_key=public_your_site_key_here&backend_url=https://fp.sa.mubeen.ai"></script>

<script type="module" src="/src/main.jsx"></script>
</body>
</html>
src/useDeviceId.js
import { useState, useEffect } from 'react';

export function useDeviceId() {
const [deviceId, setDeviceId] = useState('');
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
// Prevents late SDK resolutions from touching state after unmount.
let cancelled = false;

// ready() comes from the shim and resolves when the real SDK lands.
window.mub_fp
.ready()
.then(() => window.mub_fp.getDeviceId())
.then((id) => {
if (!cancelled) {
setDeviceId(id);
setLoading(false);
}
})
.catch((err) => {
// Fail soft — the UI surfaces the error but the app keeps working.
if (!cancelled) {
setError(err.message || 'Failed to get device ID');
setLoading(false);
}
});

return () => { cancelled = true; };
}, []);

return { deviceId, loading, error };
}
src/App.jsx
import { useState } from 'react';
import { useDeviceId } from './useDeviceId';

export default function App() {
const { deviceId, loading, error: fpError } = useDeviceId();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [submitting, setSubmitting] = useState(false);
const [result, setResult] = useState(null);

const handleSubmit = async (e) => {
e.preventDefault();
setSubmitting(true);
try {
// device_id may be '' if fingerprinting failed — server should
// treat a missing device ID as a risk signal, not a hard block.
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password, device_id: deviceId }),
});
const data = await res.json();
setResult(data);
} catch (err) {
setResult({ error: err.message });
} finally {
setSubmitting(false);
}
};

return (
<div>
<h2>Log In</h2>
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input type="email" id="email" placeholder="[email protected]"
value={email} onChange={(e) => setEmail(e.target.value)} required />

<label htmlFor="password">Password</label>
<input type="password" id="password" placeholder="Enter password"
value={password} onChange={(e) => setPassword(e.target.value)} required />

<button type="submit" disabled={submitting}>
{submitting ? 'Logging in...' : 'Log In'}
</button>
</form>

<div>
<strong>Device ID: </strong>
{fpError ? <span>Error: {fpError}</span>
: loading ? <span>Initializing...</span>
: <span>{deviceId}</span>}
</div>

{result && <pre>{JSON.stringify(result, null, 2)}</pre>}
</div>
);
}
src/main.jsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

createRoot(document.getElementById('root')).render(<App />);

Configuration Reference​

ParameterTypeRequiredDescription
site_keyStringRequiredYour unique site key (starts with public_). Passed to mub_fp.init() for Manual, or as a query parameter for Auto.
backend_urlStringRequiredRegion-specific collection endpoint — https://fp.de.mubeen.ai (EU) or https://fp.sa.mubeen.ai (KSA). Same delivery as site_key.
info

The Manual method passes credentials to mub_fp.init() in JavaScript; the Auto method passes them via the script URL query string. The values are the same for both methods.

backend_url is required

Initialization will fail if backend_url is missing. Choose the region that matches your data-residency obligations — see Regional Endpoints in the advanced reference for details.

See the HTML integration configuration reference for full details.


Troubleshooting​

useDeviceId hook returns loading indefinitely

The hook depends on the SDK scripts being loaded in index.html. Verify:

  1. The mub_fp.js SDK script is present in your index.html and loads successfully (check the Network tab)
  2. Auto method: both the inline shim and the SDK script are present, and the SDK URL includes site_key and backend_url as query parameters
  3. Manual method: src/mubFp.js is imported somewhere in your app (directly or transitively via the hook) so mub_fp.init() actually runs
  4. No Content Security Policy (CSP) rules are blocking the script
mub_fp.ready() never resolves (Auto)

This typically means the SDK script failed to load. Verify:

  1. The mub_fp.js script URL is correct and accessible
  2. The inline shim script is present and runs before the SDK script
  3. No Content Security Policy (CSP) rules are blocking the script
fingerprintReady rejects or hangs (Manual)

The mub_fp.init() call in src/mubFp.js failed. Common causes:

  1. site_key or backend_url is missing or malformed — both are required
  2. The mub_fp.js script hadn't loaded yet when src/mubFp.js was evaluated — make sure the SDK <script defer> tag appears before the React <script type="module"> in index.html
  3. The backend URL is unreachable from the browser (CORS, network, wrong region) — check the Network tab for failed POST requests
mub_fp is not defined

window.mub_fp isn't available when your code tries to use it. Ensure:

  1. Auto method: the inline shim <script> block appears in index.html before the SDK <script> tag and before your React module script
  2. Manual method: the SDK <script defer> appears before the React <script type="module"> so the SDK executes first
  3. The SDK script URL is correct and reachable — a 404 leaves window.mub_fp undefined
SDK impacts page load performance

The SDK uses defer by default and should not block rendering. If you observe performance issues:

  1. Confirm the defer attribute is present on the script tag
  2. Check that you are not calling SDK methods synchronously outside the hook
  3. Use browser DevTools Network tab to verify the script loads after DOM content

Next Steps​