Official Node.js SDK for Maskbreak — network and device fraud signals for browser-SDK-backed visits, plus limited public IP intelligence.
Using Claude Code, Cursor, Copilot, or any AI coding assistant? Paste this one prompt and it wires the whole integration — frontend script, backend check, env var, and a test:
Fetch https://maskbreak.com/integrate.md and follow it to add Maskbreak fraud protection to this app — protect signup, login, and checkout. My API key is sk_live_YOUR_KEY; put it in a SENTINEL_KEY env var, never in client-side code. Then show me how to test it.
integrate.md is the canonical
machine-readable integration guide, kept in sync with the live API.
npm install @sentinelsup/sdkZero dependencies. Requires Node.js 18+ with built-in fetch. Other runtimes and edge bundlers are not covered by this package's test matrix.
const Sentinel = require('@sentinelsup/sdk');
const sentinel = new Sentinel({ apiKey: process.env.SENTINEL_KEY });
const result = await sentinel.evaluate({
token: req.body.sentinelToken // from the frontend SDK
});
if (result.decision === 'block') {
return res.status(403).json({ error: 'blocked' });
}
// Handle 'review' according to your policy; 'allow' is not a safety guarantee.Get a free API key (no credit card) at maskbreak.com/signup.
Illustrative response. The VPN/proxy service name is returned only when known; otherwise it is null. Device fields require available device intelligence, not just a supplied event ID.
{
decision: 'review', // 'allow' | 'review' | 'block' — route on this
risk_score: 65, // 0–100
isSuspicious: true, // legacy flag; route on decision instead
ip: '198.51.100.18',
country: 'NL',
network: {
vpn: true, proxy: false, datacenter: true, anonymous: true,
tor: false, residential: false, service: 'PROTON_VPN'
},
device: { // when fingerprintEventId resolves to device data
antidetect: false, // antidetect browser detected
automation: false, // bot / browser automation
emulator: false, virtual_machine: false, incognito: false,
ip_blocklisted: false, visitor_id: 'abc123', tampering_score: 0
},
reasons: ['vpn_detected', 'datacenter_asn'] // machine-readable codes
}Try the live sample (same shape, no key needed):
curl "https://maskbreak.com/v1/evaluate/sample?scenario=vpn"
Legacy details / deviceIntel fields are still returned for backwards
compatibility with 0.1.0 integrations.
Add the Maskbreak SDK to your frontend. One script loads both layers — network (VPN/proxy/datacenter) and device (antidetect/bot/tampering):
<script async src="https://maskbreak.com/assets/sentinel.js"></script>
<!-- Add class="monocle-enriched" to any form you want evaluated -->
<form class="monocle-enriched" id="checkout-form">
<!-- The SDK injects both:
<input type="hidden" name="monocle" value="eyJ..."> (network)
<input type="hidden" name="sentinel_fp" value="a1b2..."> (device) -->
</form>Collect both and send them to your backend:
const { token, fingerprintEventId } = await window.Sentinel.collect();
fetch('/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, fingerprintEventId })
});const Sentinel = require('@sentinelsup/sdk');
const stripe = require('stripe')(process.env.STRIPE_KEY);
const sentinel = new Sentinel({ apiKey: process.env.SENTINEL_KEY });
app.post('/checkout', async (req, res) => {
const { decision } = await sentinel.evaluate({ token: req.body.token, fingerprintEventId: req.body.fingerprintEventId });
if (decision === 'block') return res.status(403).json({ error: 'declined' });
const intent = await stripe.paymentIntents.create({ /* ... */ });
res.json({ clientSecret: intent.client_secret });
});app.post('/auth/google', async (req, res) => {
const { credential, sentinelToken, fingerprintEventId } = req.body;
const ticket = await googleClient.verifyIdToken({ idToken: credential });
const result = await sentinel.evaluate({ token: sentinelToken, fingerprintEventId });
if (result.decision === 'block') return res.status(403).json({ error: 'signup_blocked' });
await createUser(ticket.getPayload().email, result.device?.visitor_id);
});// Only block when we see both residential proxy AND an antidetect browser
const blocked = await sentinel.shouldBlock(
{ token, fingerprintEventId },
r => r.network.proxy && r.network.residential && r.device?.antidetect
);// Pass the signup email and Sentinel checks it against a continuously
// refreshed disposable-domain feed. A hit adds the disposable_email
// reason, raises risk_score, and escalates allow → review. The address
// is checked transiently — never stored or logged.
const result = await sentinel.evaluate({ token, email: req.body.email });
if (result.email?.disposable) {
// e.g. require a real address before granting the trial
}// Batch scoring, log enrichment, server-side screening. Same key,
// same hourly quota as evaluate().
const info = await sentinel.lookup('185.220.101.34');
// info.verdict → 'allow' | 'review' | 'block'
// info.risk_score → 0–100
// info.signals → { vpn, proxied, tor, dch, anon } (null when known:false)
// info.network → { asn, org, country, city }| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
string | required | Your key starting with sk_live_ |
endpoint |
string | https://maskbreak.com |
Override base URL |
timeoutMs |
number | 5000 |
Per-request timeout |
Returns EvaluateResult. Throws SentinelError on network/API failure — the error carries .status and .body.
fingerprintEventId— requests device signals (tampering, automation, emulator, …). When the device is identified,device.times_seen,device.first_seenanddevice.returningdescribe its retained sightings across Maskbreak, not just your account. These records are pruned after 90 days of inactivity;first_seenis not necessarily the device's lifetime first visit.accountId— your own user id for this session; with an identified device, enables customer-scoped account linking (device.linked_accounts/device.multi_account). Links are hash-only, never cross-customer, and pruned after 90 days of inactivity.email— addsemail.disposableto the response; burner domains escalateallowtoreview.
Returns LookupResponse for a public IPv4/IPv6 address (wraps GET /v1/lookup/{ip}): a verdict, risk score and limited public-feed evidence from cloud-hosting ranges and Tor exit lists. Legacy VPN/proxy fields do not establish complete coverage. Use evaluate() with a browser SDK token for VPN/proxy evidence and service naming when known. known: false, false signals or an allow verdict are not a safety guarantee. Network metadata may be null. Shares the per-key hourly quota with evaluate().
Convenience: runs evaluate() and returns a boolean. Default predicate is r => r.decision === 'block' (honors your dashboard rules and allow/block pins). Pass your own to build custom policies.
accountId,lookup()require v0.2.1 or later (npm install @sentinelsup/sdk@latest) — the older 0.1.2 silently ignoresaccountId/
Deterministic test tokens exercise every decision path from a terminal — authenticated and rate-limited like real calls, but never billed, stored, or webhooked (responses carry "test": true):
await sentinel.evaluate({ token: 'test_vpn' }); // → engine decision: 'review' (your rules/pins may override)
await sentinel.evaluate({ token: 'test_clean' }); // → decision: 'allow' path
// also: test_proxy, test_datacenter, test_tor- No account yet? The public sandbox key
sk_test_sandboxanswers the sametest_*tokens with the same shapes — no signup, nothing stored. - CI / staging with real traffic: every account also has a personal
sk_test_…key (Settings → API Key) that runs the complete live pipeline — device intelligence, your rules and exception pins — but events are flagged as test, excluded from usage, and never fire webhooks. It is exempt from the account's IP allowlist.
Free tier: 1,000 requests/hour per API key (evaluate() and lookup() share the bucket). No monthly cap, no credit card.
On 429, the thrown SentinelError has .status === 429. This SDK exposes status and body, not HTTP response headers. If your integration needs Retry-After or X-RateLimit-*, use raw HTTP and read those headers from the response. Use bounded backoff and an endpoint-specific fallback; an unavailable check is not an allow verdict. Approved public-interest keys have no per-key hourly cap, but independent endpoint and abuse-protection limits still apply.
The current evaluate() helper requires a non-empty token and does not serialize tz. Raw HTTP accepts missing or empty tokens as degraded evaluations and supports the timezone returned by Sentinel.collect(). Use the HTTP reference for those paths; missing network evidence does not prove a visitor is safe.
npm ci --ignore-scripts
npm test
npm pack --dry-run --ignore-scripts
npm auditTests use local fixtures, not production keys. CI checks Node.js 18, 22 and 24. These commands do not publish a package.
Full types ship with the package. Importing Sentinel gives you the class plus EvaluateResult, DeviceIntel, EvaluateDetails, and SentinelError types.
import Sentinel, { EvaluateResult } from '@sentinelsup/sdk';SDK-backed visits can supply VPN/proxy, cloud-hosting and Tor signals, with VPN/proxy service names when known. Available device intelligence adds browser tampering, automation, emulator and virtual-machine signals. Coverage depends on the evidence available; these are not guarantees of detecting every product or visitor. Bare-IP lookup is limited to public cloud-range and Tor evidence.
- Python SDK —
sentinelsupon PyPI - Free IP lookup tool — maskbreak.com/ip-lookup
MIT © Sentinel Edge Networks LTD
- Website — maskbreak.com
- API docs — maskbreak.com/api
- Blog — maskbreak.com/blog