### Evaluate token with Python Sentinel SDK Source: https://maskbreak.com/api Python SDK example for the MaskBreak evaluate endpoint. Requires `pip install sentinelsup`. The SDK reads the `SENTINEL_KEY` environment variable if no API key is passed. It requires a non-empty token; use raw HTTP for degraded fallback or timezone support. The result exposes `decision` ('allow' | 'review' | 'block') and `reasons`; `is_blocked` triggers a 403 response. ```python # pip install sentinelsup from sentinel import Sentinel s = Sentinel(api_key='sk_live_YOUR_API_KEY') # or omit — reads SENTINEL_KEY env var # request_body is the JSON body parsed by your web framework. # This SDK requires a non-empty token; use raw HTTP for degraded fallback or tz. result = s.evaluate( token=request_body.get('token', ''), fingerprint_event_id=request_body.get('fingerprintEventId') ) # result.decision → 'allow' | 'review' | 'block' — route on this # result.reasons → machine-readable why if result.is_blocked: return '403 Suspicious connection', 403 ``` -------------------------------- ### GET /v1/evaluate/sample with scenario=clean Source: https://maskbreak.com/api Sandbox endpoint for testing the response shape without an API key. Returns sample data for the 'clean' scenario. Rate-limited to 30 requests per minute. ```http GET https://maskbreak.com/v1/evaluate/sample ?scenario=clean ``` -------------------------------- ### 200 OK Clean connection response Source: https://maskbreak.com/api Example response for a clean connection: no suspicious signals, decision "allow", risk_score 0, and empty reasons array. ```json { "status": "success", "isSuspicious": false, "decision": "allow", "risk_score": 0, "ip": "82.131.45.9", "country": "DE", "network": { "vpn": false, "proxy": false, "datacenter": false, "tor": false, "anonymous": false, "residential": true, "service": null }, "reasons": [], "evaluated_in_ms": 118 } ``` -------------------------------- ### GET /v1/usage Source: https://maskbreak.com/api Documentation for the GET /v1/usage endpoint, which retrieves API key quota usage information. ```APIDOC ## GET /v1/usage ### Description Retrieves the current API key's quota usage information, including hourly usage, limits, and account totals. This endpoint is free and does not consume evaluation quota. ### Method GET ### Endpoint /v1/usage ### Parameters #### Authentication - **Authorization** (string) - Required - Bearer token with the API key (e.g., `sk_live_YOUR_API_KEY`). Works with both `sk_live_` and `sk_test_` keys. ### Request Example ``` curl https://maskbreak.com/v1/usage \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" ``` ### Response #### Success Response (200) - **key_type** (string) - The type of API key used: "live" or "test". - **hourly_limit** (integer) - The hourly quota limit for the key. - **used_this_hour** (integer) - The number of evaluations used in the current hourly window. Advisory; resets on deploy. - **remaining** (integer) - The remaining evaluations in the current hourly window. Advisory; resets on deploy. - **resets_at** (string) - ISO 8601 timestamp of when the hourly window resets; null if nothing used yet. - **total_evaluations** (integer) - Durable total number of evaluations for the account. - **limit_hits** (integer) - Durable count of times the limit has been hit. #### Response Example ``` { "key_type": "live", "hourly_limit": 1000, "used_this_hour": 412, "remaining": 588, "resets_at": "2026-07-19T11:00:00.000Z", "total_evaluations": 183204, "limit_hits": 3 } ``` ### Error Responses - **429** - Rate limit exceeded. The endpoint is limited to 120 reads/minute per address. Includes `Retry-After` header. Also returned for repeated invalid credentials (15-minute lockout, `Retry-After: 900`). - **403** - Suspension. The endpoint does not enforce the key's IP allowlist; suspension still returns 403. ``` -------------------------------- ### Send event to /v1/evaluate for customer history Source: https://maskbreak.com/api Call /v1/evaluate from your backend with a live API key to get device.customer_history. accountId must come from your authenticated session, not the browser. The response includes visits, returning, first_seen, last_seen, accounts, and window_days:90. History is not authorization; use your existing policy. ```javascript // Your backend: accountId comes from YOUR authenticated session. const response = await fetch('https://maskbreak.com/v1/evaluate', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.MASKBREAK_API_KEY, 'Content-Type': 'application/json' }, signal: AbortSignal.timeout(8000), body: JSON.stringify({ token: req.body.token, fingerprintEventId: req.body.fingerprintEventId, accountId: req.user ? String(req.user.id) : undefined, gpuEvidence: req.body.gpuEvidence // optional beta; otherwise omit }) }); if (!response.ok) throw new Error('Visitor check unavailable'); const result = await response.json(); const history = result.device?.customer_history; // history: {visits, returning, first_seen, last_seen, accounts, window_days:90} // Use your existing decision/review policy. History is not authorization. ``` -------------------------------- ### Evaluate token with PHP Sentinel SDK Source: https://maskbreak.com/api PHP SDK example for the MaskBreak evaluate endpoint. Requires `composer require sentinelsup/sdk`. The client reads the `SENTINEL_KEY` environment variable if no key is passed. Expects a JSON request body (not URL-encoded). Requires a non-empty token; use raw HTTP for degraded fallback or timezone support. The result exposes `decision` and `reasons`; `isBlocked()` triggers a 403 response. ```php // composer require sentinelsup/sdk $sentinel = new \Sentinel\Client(); // or pass the key — reads SENTINEL_KEY env var // The frontend sends JSON, not a URL-encoded form. // This SDK requires a non-empty token; use raw HTTP for degraded fallback or tz. $input = json_decode(file_get_contents('php://input'), true, 512, JSON_THROW_ON_ERROR); $result = $sentinel->evaluate([ 'token' => $input['token'] ?? '', 'fingerprintEventId' => $input['fingerprintEventId'] ?? null, ]); // $result->decision → 'allow' | 'review' | 'block' — route on this // $result->reasons → machine-readable why if ($result->isBlocked()) { http_response_code(403); exit(); } ``` -------------------------------- ### GET /v1/lookup/{ip} Source: https://maskbreak.com/api Documentation for the IP Lookup endpoint, which returns a verdict for a bare IP address without a browser token. ```APIDOC ## GET /v1/lookup/{ip} ### Description Returns a verdict for a bare IP address without a browser token. Used for server-side screening such as allowlist checks, batch scoring, and enriching logs. Network signals only; VPN and proxy tunnels are not detected on a bare IP. ### Method GET ### Endpoint /v1/lookup/{ip} ### Parameters #### Path Parameters - **ip** (string) - Required - The IP address to look up. ### Request Example ``` curl https://maskbreak.com/v1/lookup/185.220.101.34 \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" ``` ### Response #### Success Response (200) - **ip** (string) - The IP address queried. - **known** (boolean) - Whether any intelligence source had an opinion. - **verdict** (string) - allow, review, or block. - **risk_score** (integer) - Risk score from 0 to 100. - **signals** (object) - Object with boolean fields: vpn, proxied, tor, dch, anon. - **network** (object) - Object with fields: asn, org, country, city, registration_country, source. - **latency_ms** (integer) - Latency in milliseconds. #### Response Example ``` { "ip": "185.220.101.34", "known": true, "verdict": "block", "risk_score": 90, "signals": { "vpn": false, "proxied": false, "tor": true, "dch": false, "anon": true }, "network": { "asn": 60729, "org": "Tor exit node", "country": null, "city": null, "registration_country": "DE", "source": "asn_table" }, "latency_ms": 12 } ``` ``` -------------------------------- ### POST /api/lookup with IP Source: https://maskbreak.com/api Look up a real IP address without an API key. The prefilled IP is a Tor exit. Tightly rate-limited to a few tries per minute; use GET /v1/lookup with a key for volume. ```http POST https://maskbreak.com/api/lookup { "ip": "185.220.101.34" } ``` -------------------------------- ### 200 OK Suspicious connection response Source: https://maskbreak.com/api Example response for a suspicious connection: VPN and datacenter signals produce decision "review" with risk_score 65 and reasons ["vpn_detected", "datacenter_asn"]. ```json { "status": "success", "isSuspicious": true, "decision": "review", "risk_score": 65, "ip": "185.107.80.12", "country": "EE", "network": { "vpn": true, "proxy": false, "datacenter": true, "tor": false, "anonymous": true, "residential": false, "service": "PROTON_VPN" }, "reasons": ["vpn_detected", "datacenter_asn"], "evaluated_in_ms": 126, "details": { "ip": "185.107.80.12", "cc": "EE", "vpn": true, "proxied": false, "anon": true, "dch": true, "service": "PROTON_VPN" } } ``` -------------------------------- ### IP Lookup with curl: GET /v1/lookup/{ip} Source: https://maskbreak.com/api Use this curl command to call the IP Lookup endpoint for a bare IP address. The response shows a block verdict for a Tor exit node, with network signals and a risk score. Note that on a bare IP, VPN and proxy signals are false; only Tor-exit and cloud-range checks fire. ```bash curl https://maskbreak.com/v1/lookup/185.220.101.34 \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" { "ip": "185.220.101.34", "known": true, "verdict": "block", // allow | review | block "risk_score": 90, "signals": { "vpn": false, "proxied": false, "tor": true, "dch": false, "anon": true }, "network": { "asn": 60729, "org": "Tor exit node", "country": null, "city": null, "registration_country": "DE", "source": "asn_table" }, "latency_ms": 12 } ``` -------------------------------- ### POST /v1/device-evidence/challenge Source: https://maskbreak.com/api Requests a WebGPU challenge from your backend using a live API key. The challenge is required for the optional WebGPU resemblance beta. ```APIDOC ## POST /v1/device-evidence/challenge ### Description Requests a WebGPU challenge for a provider-verified event. The challenge expires in two minutes and is bound to the customer, device, and event. ### Method POST ### Endpoint https://maskbreak.com/v1/device-evidence/challenge ### Parameters #### Request Body - **fingerprintEventId** (string) - Required - The fingerprint event identifier. ### Request Example ```json { "fingerprintEventId": "..." } ``` ### Response #### Success Response (200) - **challenge** (string) - The challenge token. - **expires_at** (string) - Expiration timestamp. - **protocol** (string) - Protocol identifier. #### Response Example ```json { "challenge": "...", "expires_at": "2025-01-10T00:00:00Z", "protocol": "..." } ``` ``` -------------------------------- ### Sample response placeholder Source: https://maskbreak.com/api Placeholder response shown before running the sample request. Clicking 'Run Request' returns live JSON with the same shape as production responses. ```json { "click ▶ Run Request": "to see live JSON" } ``` -------------------------------- ### POST /v1/evaluate Source: https://maskbreak.com/api Sends a fresh, real browser event to /v1/evaluate with a live key. Returns device history and account links for your own visitors. ```APIDOC ## POST /v1/evaluate ### Description Sends a fresh, real browser event to evaluate a visitor. Returns device history and account links for your own visitors. ### Method POST ### Endpoint https://maskbreak.com/v1/evaluate ### Parameters #### Request Body - **token** (string) - Required - The token from the browser SDK. - **fingerprintEventId** (string) - Required - The fingerprint event identifier. - **accountId** (string) - Optional - The account ID from your authenticated session. Must be set from your own backend, never from the browser. - **gpuEvidence** (object) - Optional - Beta WebGPU evidence object. Omit if not used. ### Request Example ```json { "token": "...", "fingerprintEventId": "...", "accountId": "123", "gpuEvidence": {} } ``` ### Response #### Success Response (200) - **device.customer_history** (object) - Contains device history fields. - **visits** (number) - Count of distinct verified events in the last 90 days. - **returning** (boolean) - Whether the device is returning. - **first_seen** (string) - First visit timestamp. - **last_seen** (string) - Latest visit timestamp. - **accounts** (array) - Associated account IDs. - **window_days** (number) - Always 90. #### Response Example ```json { "device": { "customer_history": { "visits": 3, "returning": true, "first_seen": "2025-01-01T00:00:00Z", "last_seen": "2025-01-10T00:00:00Z", "accounts": ["123", "456"], "window_days": 90 } } } ``` ``` -------------------------------- ### Request WebGPU challenge and collect evidence Source: https://maskbreak.com/api Two-step WebGPU resemblance beta flow. Backend requests a challenge from /v1/device-evidence/challenge with a live key; the challenge expires in two minutes and requires a provider-verified event under five minutes old. Browser loads the separate collector and calls MaskbreakGPU.collect(challenge). Never send your API key to the browser; forward only successful evidence with the same fingerprintEventId. ```javascript // 1. Your backend requests a challenge using its LIVE API key. // Protect your own route with session/CSRF checks and rate limits. const response = await fetch('https://maskbreak.com/v1/device-evidence/challenge', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.MASKBREAK_API_KEY, 'Content-Type': 'application/json' }, signal: AbortSignal.timeout(6000), body: JSON.stringify({ fingerprintEventId: req.body.fingerprintEventId }) }); if (!response.ok) return res.status(200).json({ status: 'unavailable' }); return res.json(await response.json()); // challenge, expires_at, protocol ``` ```javascript // 2. Browser, after your required consent/notice flow: // Load https://maskbreak.com/assets/device-evidence.js?v=1 separately. // Pass the challenge from YOUR backend; never send your API key here. const gpuEvidence = challenge ? await MaskbreakGPU.collect(challenge) : { status: 'unavailable' }; // Forward only successful evidence with the SAME fingerprintEventId: // gpuEvidence: gpuEvidence.features ? gpuEvidence : undefined // If unavailable or your challenge route fails, continue normal checks. ``` -------------------------------- ### Test evaluate endpoint with sandbox key Source: https://maskbreak.com/api Use the public sandbox key sk_test_sandbox to test the evaluate endpoint without an account. It only accepts test_* tokens, is rate-limited per IP, and never bills or stores anything. ```bash curl -X POST https://maskbreak.com/v1/evaluate -H "Authorization: Bearer sk_test_sandbox" -H "Content-Type: application/json" -d '{"token":"test_vpn"}' ``` -------------------------------- ### Lookup response placeholder Source: https://maskbreak.com/api Placeholder response shown before running the lookup. Clicking 'Run Lookup' returns a live verdict for the IP. ```json { "click ▶ Run Lookup": "for a live verdict on this IP" } ``` -------------------------------- ### Collect and forward browserEvidence for browser-consistency check Source: https://maskbreak.com/api Use this optional browser-consistency diagnostic on an opted-in review step after required notice/consent. It collects browserEvidence with a one-use challenge from your authenticated backend, forwards it to POST /v1/evaluate, and reads result.device?.browser_consistency. Send either browserEvidence or gpuEvidence, not both; the report is experimental and does not affect decisions. ```javascript // Browser: load /assets/browser-consistency.js?v=1 from maskbreak.com. // Run only on an opted-in review step, after required notice/consent. // Obtain the one-use challenge from YOUR authenticated backend route above. const browserEvidence = challenge ? await MaskbreakBrowserConsistency.collect(challenge) : { status: 'unavailable' }; // Forward to YOUR backend with the same session-bound fingerprintEventId. // Do not expose a Maskbreak API key in browser code. // Backend: optional field in the existing POST /v1/evaluate JSON body: // browserEvidence: browserEvidence.protocol ? browserEvidence : undefined // Send either browserEvidence OR gpuEvidence for a device event, not both. // Read result.device?.browser_consistency: // {status:'inconsistent', experimental:true, source:'browser_reported', // affects_decision:false, findings:['browser_context'], checks:[...]} // Keep the original decision/review policy. This report is not authorization. ``` -------------------------------- ### POST https://maskbreak.com/mcp Source: https://maskbreak.com/api Documentation for the hosted Model Context Protocol server endpoint, including authentication methods and available tools. ```APIDOC ## POST https://maskbreak.com/mcp ### Description A hosted Model Context Protocol server that allows AI agents to screen IPs with real Maskbreak verdicts. It provides two tools: `lookup_ip` (same verdict as `GET /v1/lookup`) and `service_status`. ### Method POST ### Endpoint https://maskbreak.com/mcp ### Authentication - **Authorization: Bearer sk_live_...** (API key) - Required for authenticated calls. Anonymous use is allowed but subject to tight per-IP limits. - **OAuth access_token** from `POST /oauth/token` - Alternative authentication method. ### Request Example ```json { "mcpServers": { "sentinel": { "type": "http", "url": "https://maskbreak.com/mcp", "headers": { "Authorization": "Bearer sk_live_YOUR_API_KEY" } } } } ``` ### Response #### Success Response - **lookup_ip** (tool) - Returns the same verdict as `GET /v1/lookup`. - **service_status** (tool) - Returns the service status. ### Notes - Streamable HTTP, stateless. - Authenticated calls run on your 1,000/hour quota; your IP allowlist applies. - Anonymous use shares the free tool's tight per-IP limits. ``` -------------------------------- ### POST /v1/evaluate Source: https://maskbreak.com/api Documentation for the POST /v1/evaluate endpoint, including request body parameters, testing tokens, and response fields. ```APIDOC ## POST /v1/evaluate ### Description Evaluates a network token and optional device intelligence to return a risk decision (allow, review, block). Call from your backend server only, never from the browser. ### Method POST ### Endpoint https://maskbreak.com/v1/evaluate ### Parameters #### Request Body - **token** (string) - Required - The encrypted network token from `Sentinel.collect()` or `input[name="monocle"]`. Send it for full network intelligence. Raw HTTP accepts a missing or empty token as a degraded evaluation; a non-string or provider-rejected token returns 400. The current Node, Python and PHP server SDKs require a non-empty token locally, so use raw HTTP for this fallback. - **fingerprintEventId** (string) - Optional - The device-intelligence event id — the SDK captures it and injects it as `sentinel_fp`; forward it here (either name is accepted) to unlock the `device.*` signals (antidetect, automation, emulator, …). Omit it and you get a network-only verdict. - **accountId** (string) - Optional - Your own account/user id for this session. Combined with `fingerprintEventId`, Maskbreak counts how many distinct accounts this device has been linked to under your API key and returns `device.linked_accounts` / `device.multi_account` — multi-accounting detection with zero extra integration. Stored as a one-way hash only. - **email** (string) - Optional - The email the visitor is signing up with. Adds `email.disposable` to the response — checked against a continuously refreshed feed of thousands of burner/disposable domains. A disposable hit adds the `disposable_email` reason, raises `risk_score`, and escalates `allow` to `review`. The address is checked transiently and never stored or logged. - **tz** (string) - Optional - The visitor's IANA timezone, e.g. `Europe/Tallinn`. Our client SDK sends it automatically as `sentinel_tz`, which this endpoint also accepts; on your own you can read it with `Intl.DateTimeFormat().resolvedOptions().timeZone` — no permission prompt and no geolocation involved. Adds a `timezone` block to the response. When the connection looks ordinary but the browser's clock belongs to a different country, that is the shape of a residential proxy the network layer did not catch: it adds the `timezone_mismatch` reason, raises `risk_score`, and escalates `allow` to `review`. Deliberately **not** raised on a VPN, proxy, Tor or datacenter exit — disagreeing with the clock is what those do — and never enough to `block` on its own, because travellers and expatriates land here legitimately. Forward the tz returned by Sentinel.collect() in your raw HTTP request. The current server SDK helpers do not serialize tz; use raw HTTP when you need this field. ### Request Example ```json { "token": "test_vpn", "fingerprintEventId": "...", "tz": "Europe/Tallinn" } ``` ### Response #### Success Response (200) - **decision** (string) - `allow` | `review` | `block` (covers VPN, proxy, datacenter, Tor, antidetect, automation) — route on this - **reasons** (array) - Machine-readable why - **test** (boolean) - Present and `true` when using a test token or test key #### Response Example ```json { "decision": "block", "reasons": ["vpn"], "test": true } ``` ``` -------------------------------- ### Evaluate endpoint with fetch (Node.js) Source: https://maskbreak.com/api Call the evaluate endpoint from a backend server using fetch. Pass the token, fingerprintEventId, and tz from the client. The response decision is 'allow', 'review', or 'block'; block should return a 403. ```javascript const response = await fetch('https://maskbreak.com/v1/evaluate', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ token: req.body.token, // as sent by your frontend (see Quick Start Step 2) fingerprintEventId: req.body.fingerprintEventId, tz: req.body.tz }) }); const data = await response.json(); // data.decision → 'allow' | 'review' | 'block' (covers VPN, proxy, // datacenter, Tor, antidetect, automation) — route on this // data.reasons → machine-readable why if (data.decision === 'block') { return res.status(403).json({ error: 'Suspicious connection.' }); } ``` -------------------------------- ### Collect tokens with Sentinel.collect() on submit Source: https://maskbreak.com/api On form submit, call window.Sentinel.collect() which waits for the device layer to settle and returns { token, fingerprintEventId }. If token is falsy, the network SDK is still loading and you should ask the user to try again. Forward both values to your backend along with the form data. ```javascript document.getElementById('my-form').addEventListener('submit', async (e) => { e.preventDefault(); // Both layers: network token + device event id const { token, fingerprintEventId } = await window.Sentinel.collect(); if (!token) { // Network SDK still loading — ask user to try again return showError('Security check loading. Please try again.'); } // Forward both to your backend with the rest of the form data const res = await fetch('/your-backend-endpoint', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: e.target.email.value, token, fingerprintEventId // ← both layers to your backend }) }); }); ``` -------------------------------- ### Check API usage with curl Source: https://maskbreak.com/api Poll the usage endpoint with a live API key. The response shows the hourly bucket, advisory counters, and durable totals. The call is free and does not consume quota, but is limited to 120 reads per minute per address. ```bash curl https://maskbreak.com/v1/usage \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" { "key_type": "live", // or "test" "hourly_limit": 1000, "used_this_hour": 412, "remaining": 588, "resets_at": "2026-07-19T11:00:00.000Z", // end of the rolling 60 minutes that began with your first call; null if nothing used yet "total_evaluations": 183204, "limit_hits": 3 } ``` -------------------------------- ### Add Maskbreak check to existing login handler (HTML + frontend + Express) Source: https://maskbreak.com/api Complete integration pattern for adding Maskbreak to an existing login handler. Includes the HTML collector script, frontend collection of token/fingerprintEventId/tz, and a Node 22/Express backend that calls https://maskbreak.com/v1/evaluate with a 2-second timeout. The backend enforces the decision: returns 403 for 'block', 409 with next:'step_up' for 'review' or degraded, and passes through to the existing login handler otherwise. Never authorizes from test/sandbox/sample results and never creates a session; use a live key in MASKBREAK_API_KEY. ```javascript // STEP 1 — HTML. Load the collector before your form handler.
// STEP 2 — Your frontend script, after the SDK loads. document.getElementById('login-form').addEventListener('submit', async (event) => { event.preventDefault(); const { token, fingerprintEventId, tz } = await window.Sentinel.collect(); const response = await fetch('/your-login-endpoint', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: event.target.email.value, token, fingerprintEventId, tz }) }); // Handle response.status and the next step in your existing login UI. }); // STEP 3 — Node 22 / Express backend. // express, app and existingLoginHandler belong to your application. // This check is NOT authentication and never creates a login session. app.use(express.json({ limit: '16kb' })); app.post('/your-login-endpoint', async (req, res, next) => { try { const { token, fingerprintEventId, tz, email } = req.body; const response = await fetch('https://maskbreak.com/v1/evaluate', { method: 'POST', signal: AbortSignal.timeout(2000), // example budget; measure your own headers: { 'Authorization': 'Bearer ' + process.env.MASKBREAK_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ token, fingerprintEventId, tz, email }) }); if (!response.ok) throw new Error('Visitor check unavailable'); const result = await response.json(); // Production gate: never authorize from a fixture or personal test key. // Use a live key in MASKBREAK_API_KEY; test the integration separately. if (result.test || result.sandbox || result.sample) { throw new Error('Synthetic visitor check'); } if (!['allow', 'review', 'block'].includes(result.decision)) { throw new Error('Unexpected verdict'); } if (result.decision === 'block') { return res.status(403).json({ error: 'Action refused by policy' }); } if (result.decision === 'review' || result.degraded) { // Implement an actual step-up flow; never mint a session here. return res.status(409).json({ next: 'step_up' }); } req.fraudCheck = result; return next(); // normal credential + authorization checks still run } catch { // Example temporary hold. Choose a fallback for each business action. return res.status(503).json({ error: 'Check unavailable; try again shortly' }); } }, existingLoginHandler); ``` -------------------------------- ### Exchange email and API key for OAuth 2.0 bearer token Source: https://maskbreak.com/api Exchange your account email and API key for a short-lived bearer token using the client_credentials grant. The token lives for 60 minutes and keeps working through key rotation. Use the returned access_token in the Authorization: Bearer header anywhere an API key is accepted. ```bash curl -X POST https://maskbreak.com/oauth/token \ -H "Content-Type: application/json" \ -d '{"grant_type":"client_credentials","client_id":"your@email.com","client_secret":"sk_live_..."}' ``` -------------------------------- ### Webhook Delivery Source: https://maskbreak.com/api Documentation for the webhook delivery mechanism, including payload structure, headers, retry behavior, and deduplication guidance. ```APIDOC ## Webhook Delivery ### Description Webhook events are delivered to your configured endpoint. Each event is attempted up to 3 times with retries after roughly 1 minute and then roughly 8 minutes. Retries are best-effort and in-process; a deploy mid-retry drops remaining attempts. The dashboard Events log is the source of truth. Deliveries time out after 10 seconds and never follow redirects. ### Method POST (implied by webhook delivery) ### Endpoint Your configured webhook URL (Slack/Discord URLs receive a formatted message instead of the generic payload) ### Headers - **X-Maskbreak-Event-Id** (string) - Required - The event ID, same as the `event_id` field in the payload. Present on every delivery. ### Request Body (Generic Endpoint Payload) - **event** (string) - Required - Event type, e.g., `threat.detected`. - **event_id** (string) - Required - 32 hex chars, unique per event; same on retries. - **timestamp** (string) - Required - ISO 8601 timestamp of the event. - **ip** (string) - Required - The IP address that triggered the event. - **threat_type** (string) - Required - Type of threat, e.g., `Tor`, `Custom rule (…)`, or `Customer exception (…)`. - **details** (object) - Required - Additional details about the event. - **ip** (string) - Required - The IP address. - **cc** (string) - Required - Country code. - **vpn** (boolean) - Required - Whether VPN was detected. - **proxied** (boolean) - Required - Whether proxy was detected. - **tor** (boolean) - Required - Whether Tor was detected. - **dch** (boolean) - Required - Whether data center hosting was detected. - **bot** (boolean) - Required - Whether bot was detected. - **tampering** (boolean) - Required - Whether tampering was detected. - **antidetect** (boolean) - Required - Whether antidetect was detected. - **decision** (string) - Required - The decision, e.g., `block`. - **rule_matched** (string or null) - Optional - The rule that caused the block, if any. - **exception_matched** (string or null) - Optional - The exception that caused the block, if any. ### Request Example ```json { "event": "threat.detected", "event_id": "9f2ce7a4c1d84b7fa3d2c05e8b6f4a19", "timestamp": "2026-07-19T09:14:03.512Z", "ip": "185.220.101.34", "threat_type": "Tor", "details": { "ip": "185.220.101.34", "cc": "DE", "vpn": false, "proxied": false, "tor": true, "dch": false, "bot": false, "tampering": false, "antidetect": false, "decision": "block", "rule_matched": null, "exception_matched": null } } ``` ### Response #### Success Response (2xx) - No specific response body documented. A successful delivery is acknowledged by the receiver. #### Error Handling - Failed deliveries are retried up to 3 times (after ~1 min, then ~8 min). - Deliveries time out after 10 seconds and never follow redirects. - If 5 consecutive deliveries fail, an email notification is sent. - The dashboard Events log is the source of truth; the webhook is best-effort. ### Deduplication - Every payload carries an `event_id` (32 hex chars, unique per event, present on test events). - Retried deliveries reuse the same `event_id`. - Key your processing on `event_id` to handle each event exactly once. ### Delivery Logs - The 25 most recent deliveries (event, outcome, error) are listed in the dashboard Tools drawer, retained 30 days. - The header chip shows current health. ```