Client-Side Integration (React)
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.
useDeviceIdHook — ReturnsdeviceId,loading, anderrorstates- 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
| Requirement | Details |
|---|---|
| Site Key | Starts with public_ — obtain from your Mubeen dashboard |
| Backend URL | Region-specific collection endpoint — https://fp.de.mubeen.ai (EU) or https://fp.sa.mubeen.ai (KSA). Required on every initialization. |
| React | 16.8+ (requires hooks support) |
| Browser Support | Modern 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.
| Manual | Auto | |
|---|---|---|
| Script | mub_fp.js | mub_fp.js?site_key=...&backend_url=... |
| Initialization | Explicit mub_fp.init() in a bootstrap module | Automatic on script load |
| Credentials | Passed in the init() config object from JS | Passed as URL query parameters in HTML |
| Ready pattern | Hook awaits the init promise | Hook awaits mub_fp.ready() |
| Best for | Dynamic config (e.g. site key fetched at runtime), consent-gated init | Static configuration, quick setup |
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
<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
<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.
// 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
});
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().
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).
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>
);
}
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.
<!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>
// 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
});
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 };
}
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>
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>
);
}
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.
<!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>
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 };
}
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>
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>
);
}
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')).render(<App />);
Configuration Reference
| Parameter | Type | Required | Description |
|---|---|---|---|
site_key | String | Required | Your unique site key (starts with public_). Passed to mub_fp.init() for Manual, or as a query parameter for Auto. |
backend_url | String | Required | Region-specific collection endpoint — https://fp.de.mubeen.ai (EU) or https://fp.sa.mubeen.ai (KSA). Same delivery as site_key. |
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 requiredInitialization 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:
- The
mub_fp.jsSDK script is present in yourindex.htmland loads successfully (check the Network tab) - Auto method: both the inline shim and the SDK script are present, and the SDK URL includes
site_keyandbackend_urlas query parameters - Manual method:
src/mubFp.jsis imported somewhere in your app (directly or transitively via the hook) somub_fp.init()actually runs - 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:
- The
mub_fp.jsscript URL is correct and accessible - The inline shim script is present and runs before the SDK script
- 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:
site_keyorbackend_urlis missing or malformed — both are required- The
mub_fp.jsscript hadn't loaded yet whensrc/mubFp.jswas evaluated — make sure the SDK<script defer>tag appears before the React<script type="module">inindex.html - The backend URL is unreachable from the browser (CORS, network, wrong region) — check the Network tab for failed
POSTrequests
mub_fp is not defined
window.mub_fp isn't available when your code tries to use it. Ensure:
- Auto method: the inline shim
<script>block appears inindex.htmlbefore the SDK<script>tag and before your React module script - Manual method: the SDK
<script defer>appears before the React<script type="module">so the SDK executes first - The SDK script URL is correct and reachable — a 404 leaves
window.mub_fpundefined
SDK impacts page load performance
The SDK uses defer by default and should not block rendering. If you observe performance issues:
- Confirm the
deferattribute is present on the script tag - Check that you are not calling SDK methods synchronously outside the hook
- Use browser DevTools Network tab to verify the script loads after DOM content
Next Steps
- Server-Side Integration — Send the device ID to your backend for risk validation
- Android Integration — Native Android SDK for mobile applications