A fraud detection API is the developer-facing layer of a device intelligence or risk-scoring platform. The API call lives on the critical path of your login, signup, checkout, or sensitive-action endpoint, so the design choices the API makes (synchronous or asynchronous, sealed or transparent, single-call or session-attached) propagate into your application architecture. This article covers what to expect, what to design around, and the integration patterns that have proven out across the major vendors.
This is the developer-API view. The use-case context is in payment fraud detection, ecommerce fraud prevention, account takeover prevention, and bot detection.
What a fraud detection API actually returns
Strip away the marketing copy and a fraud detection API call returns three things:
- An identifier. A stable identifier for the entity being scored. Usually the device (a visitor fingerprint or device ID), sometimes the session, sometimes the user.
- A classification. What the entity is. Human, bot, named framework, agent, anti-detect browser. Sometimes a numeric trust tier.
- A risk score with explanations. A numeric probability or risk tier, plus a list of signals that contributed to it so the result is auditable.
That structure is the same across Sift, Sardine, SEON, IPQS, ThreatMetrix, Foil and the rest. The differences are in coverage (which signals the platform collects), accuracy (how well the score predicts outcomes), and integration ergonomics (how cleanly the API fits a normal HTTP request handler).
A representative response shape (Foil’s session detail, simplified):
{
"decision": {
"automation_status": "automated",
"risk_score": 0.91,
"decision_status": "final"
},
"visitor_fingerprint": {
"id": "vid_8h2x9k3m",
"confidence": 0.94,
"lifecycle": {
"first_seen_at": "2026-05-12T09:30:11Z",
"last_seen_at": "2026-06-09T16:14:02Z",
"seen_count": 3
}
},
"attribution": {
"labels": [
{ "kind": "tool", "value": "playwright", "label": "Playwright", "confidence": 0.97 }
]
},
"network": {
"anonymity": { "vpn": false, "proxy": false, "hosting": true }
}
}
The attribution.labels array is the part that matters most for production use, because it is what makes the verdict explainable when a customer disputes a block.
Synchronous vs asynchronous scoring
The biggest architectural choice is whether the score is returned on the same request as the user-facing action, or asynchronously after the action.
Synchronous
The application obtains the verdict inside the request handler and uses it to decide what to do. In Foil’s case the browser SDK produces a sealed token that the page submits with the action, and the server verifies it inline:
import { safeVerifyFoilToken } from "@abxy/foil-server";
app.post('/api/login', (req, res) => {
const result = safeVerifyFoilToken(req.body.foilToken, process.env.FOIL_SECRET_KEY);
if (!result.ok) return blockOrChallenge(req, res);
const { decision } = result.data;
if (decision.verdict === 'bot' || decision.risk_score >= 0.8) {
return blockOrChallenge(req, res);
}
return loginContinue(req, res);
});
Pros: the decision is made before the side effect (login, signup, checkout) happens. The API has the full context of the request.
Cons: the API call is on the user-visible latency path. If the API is slow, the user-facing endpoint is slow. If the API is down, the application has to fall back.
This is the pattern that fits high-stakes endpoints (checkout, login, signup) where the decision needs to happen synchronously.
Asynchronous
The application lets the action through, then consumes the richer verdict afterward: either by looking the session up once deeper evaluation completes, or by handling a webhook event delivered to an endpoint registered for the purpose (with Foil, via client.webhooks.createEndpoint).
import { Foil } from "@abxy/foil-server";
const client = new Foil({ secretKey: process.env.FOIL_SECRET_KEY });
// In the request handler: act now, enqueue the session for follow-up
followUpQueue.push({ sessionId, userId });
return loginContinue(req, res);
// Background worker: pull the enriched session once final scoring lands
const session = await client.sessions.get(job.sessionId);
if (session.decision.automation_status === 'automated'
&& session.decision.decision_status === 'final') {
await sessions.forceLogout(job.userId);
await sendStepUpEmail(job.userId);
}
Pros: no latency on the user-facing path. The API can take longer to compute richer scores.
Cons: the decision is made after the action has already happened. Useful for monitoring and post-hoc intervention, but the user has already logged in by the time the score is available.
In practice, most production deployments use both. Synchronous scoring at the action point, asynchronous follow-up for richer analysis and longer-term pattern detection.
Sealed scoring vs transparent
A second architectural choice is whether the scoring logic lives in the client or the server.
Sealed (server-side). The client SDK collects raw signals and sends them to the API. The API runs the scoring model and returns the verdict. The model is never exposed to the client.
Transparent (client-side or open-source). The client library computes the fingerprint and the score, returns the verdict directly. The model is visible to anyone who reads the code.
Sealed is more resistant to attack. An attacker who can read the scoring logic can engineer their input to produce a clean score, even if the underlying session is malicious. Open-source fingerprinting libraries are good for understanding what is collected, but they degrade in effectiveness against sustained attack precisely because the rules are public.
Transparent is more auditable. The application owner can read the source, understand the verdict, and debug edge cases without depending on the vendor.
The modern compromise is sealed scoring with detailed explanations. The model logic stays sealed; the verdict comes back with enough explanation that the application owner can understand and defend the decisions, but not enough to reverse-engineer the model.
Single-call vs session-attached
A third architectural choice is whether the API is called once per request or whether it maintains a session-level context across many requests.
Single-call. Each API call is independent. The application passes all the context (device data, user ID, action type) in the call, gets a verdict back, and that is the end of the interaction.
Session-attached. The client SDK opens a long-lived session with the API. As the user interacts with the page, the SDK streams events to the API in the background. When the application makes a scoring call, the API uses the accumulated session context (behavioral data, fingerprint stability, page-load timing) to produce a richer score than would be possible from a single call.
Single-call is simpler to integrate, but session-attached produces better scores because the behavioral data is genuinely informative.
Most modern device intelligence platforms use session-attached. Most enrichment-only APIs (IPQS, IP reputation services, email validators) are single-call.
The three choices in summary:
| Choice | What it is | When to prefer each side |
|---|---|---|
| Sync vs async | Verdict returned inside the request handler vs computed after the action completes | Sync where the decision gates a side effect (login, signup, checkout); async for monitoring, richer analysis, and post-hoc intervention |
| Sealed vs transparent | Scoring model runs server-side and stays hidden vs visible in client code | Sealed against sustained adversaries; transparent when auditability and vendor independence matter more than attack resistance |
| Single-call vs session-attached | Independent stateless calls vs an SDK streaming session context that enriches each verdict | Single-call for enrichment lookups and simple integrations; session-attached when behavioral context should improve the score |
Webhook design that does not regret itself
If the API supports webhooks (asynchronous score push or event notification), the webhook design matters more than people realize, and the same handful of issues comes up in production again and again.
Signature verification. Every webhook should be signed with a vendor-issued secret. The application verifies the signature before processing the payload. Sift uses HMAC-SHA-256 (Sift webhook docs). Most vendors converged on the same pattern.
Idempotency. The vendor may retry webhooks. The application has to handle the same payload arriving twice without double-applying side effects. Idempotency keys in the payload, persisted by the application, are the standard solution.
Ordering. Webhooks may arrive out of order. The application’s logic should not assume webhook A arrives before webhook B unless the payload says so via timestamps or sequence IDs.
Backpressure. The application’s webhook endpoint may be slow or down. The vendor should retry with exponential backoff. The application should return 200 fast (acknowledge receipt) and process asynchronously, so a slow downstream does not back up the vendor’s delivery queue.
Replay protection. Webhooks include a timestamp; reject anything older than (e.g.) 5 minutes to prevent replay of captured webhooks.
Sandbox environments
Every reasonable fraud-detection vendor has a sandbox environment that returns realistic verdicts without affecting production scoring. The Sift documentation specifically recommends sandbox use during integration (Sift: Score API).
What a good sandbox provides:
- Deterministic responses for test inputs. Sending a known “high-risk-test” event returns a high-risk verdict every time.
- Realistic latency. The sandbox should not be artificially faster than production.
- Independent test data. Sandbox events do not bleed into production scoring; production events do not appear in sandbox results.
- Authentication separation. Sandbox API keys are clearly distinguishable from production keys.
If a vendor does not have a sandbox, integration is harder and the failure modes (an integration bug pushing real test data into production scoring) are worse.
Integration patterns that work
Three patterns from production deployments worth knowing.
Pattern 1: Score-then-act middleware
Place the verification in a middleware that runs before the route handler. The verdict is attached to the request, and the handlers read it.
import { safeVerifyFoilToken } from "@abxy/foil-server";
app.use((req, res, next) => {
if (req.path.startsWith('/api/')) {
const result = safeVerifyFoilToken(req.body?.foilToken, process.env.FOIL_SECRET_KEY);
req.foil = result.ok ? result.data : null;
}
next();
});
app.post('/api/login', (req, res) => {
if (!req.foil || req.foil.decision.verdict === 'bot') return blockOrChallenge(req, res);
// ... continue
});
This works well when many endpoints need the same verdict.
Pattern 2: Per-action thresholds with enriched lookup
The verdict shape is the same everywhere, but the policy applied to it should differ by action. Gate cheap actions on the inline decision alone, and pull the full session detail for the expensive ones.
const result = safeVerifyFoilToken(req.body.foilToken, process.env.FOIL_SECRET_KEY);
if (!result.ok || result.data.decision.risk_score >= 0.5) return blockOrChallenge(req, res);
// Checkout is high-stakes: fetch network, integrity, and attribution detail too.
const session = await client.sessions.get(result.data.session_id);
if (session.network.anonymity.hosting || session.runtime_integrity.tampering === 'high_risk') {
return requestThreeDSChallenge(req, res);
}
This pattern is useful when the same device produces traffic across many action types and the decision logic should differ accordingly.
Pattern 3: Defer scoring on non-critical paths
For routes where latency matters and the decision is not critical, skip the inline gate entirely. The browser SDK keeps streaming signals in the background regardless, so the session is still scored and can be reviewed out of band.
app.get('/api/articles/:id', (req, res) => {
// No inline gate; the SDK is still collecting and Foil is still scoring.
return serveArticle(req, res);
});
// Out of band: review what the low-stakes traffic looked like.
const session = await client.sessions.get(sessionId);
This gives the vendor enough data to detect patterns across many low-stakes events, without imposing decision latency on the user.
Error handling that does not bring you down
The API will sometimes be slow, sometimes down, sometimes returning errors. The application’s failure mode design matters.
Timeout. Set a tight timeout on the API call (a few hundred milliseconds maximum for synchronous use). If the API does not respond in time, the application should have a default behavior: usually “allow with logging” for low-stakes actions, “challenge” for medium-stakes, “queue for manual review” for high-stakes.
Circuit breaker. If the API is returning errors at a high rate, the application should stop calling it for a while and operate in degraded mode. This prevents the application from failing along with the vendor.
Default verdict. What does the application do when the API is unavailable? The right answer is operation-specific. A login route may want to fail closed (require step-up). A content-view route may want to fail open (allow with logging). Both decisions should be explicit, not accidental.
Logging. Every API call, every response, every fallback, logged with the request context. When something goes wrong, the logs are the only way to debug.
The most common integration mistakes are the mirror images of these controls: skipping webhook signature verification, so anyone who discovers the endpoint can forge verdicts; calling the API with no timeout, so a vendor incident becomes your outage; and retrying failed calls in a tight loop without backoff, which turns the vendor’s bad day into a self-inflicted one.
What to evaluate when choosing
When evaluating a fraud detection API, the technical questions worth asking go beyond “what is the catch rate.”
- What signals does the SDK collect? Specifically, not just “device data” but the actual list.
- How is the scoring done? Sealed, client-side, hybrid?
- Is there a session-attached SDK or is it single-call only?
- What is the latency on the synchronous scoring call? Ask for P99, not just the published median; tail latency is what lands on your checkout.
- What is the sandbox like?
- How are webhooks signed and retried?
- What does the explanation look like? A score with no explanation is useless when a customer is wrongly blocked.
- What is the rate of false positives reported by reference customers?
- What is the data residency and privacy posture? Especially relevant for European deployments.
- What is the per-event pricing? Per-event versus per-monthly-active-user models have very different costs depending on traffic patterns.
How Foil’s API is shaped
Foil’s API is built around a browser SDK and two server-side surfaces, from the @abxy/foil-server package.
The browser SDK streams signals to Foil in the background as the user interacts, and produces a sealed token that the page submits alongside sensitive actions.
safeVerifyFoilToken(sealedToken, secretKey) runs server-side and verifies the sealed token inline. It returns the sealed-scoring decision for the session: a verdict (bot, human, or inconclusive), a risk_score, a stable visitor_fingerprint.id, bot attribution, and the contributing signals. This is the synchronous gate.
client.sessions.get(sessionId) (on a new Foil({ secretKey }) client) is the asynchronous, enrichment path: the full session detail with attribution labels, network analysis, connection fingerprints, runtime integrity, and the visitor fingerprint’s lifecycle history. Related lookups cover the rest of the device-history surface: client.fingerprints.get(visitorId) for a visitor’s history, client.sessions.attachClientUser(sessionId, clientUserId) for device-account linking, and client.sessions.list(...) for a visitor’s sessions.
Webhooks are configured rather than registered in code: client.webhooks.createEndpoint(orgId, body) adds a delivery endpoint, client.webhooks.listEndpoints(orgId) and client.webhooks.listEvents(orgId, params) inspect them, and your application verifies and handles the events Foil delivers (finalized decisions, long-term pattern detection).
The decision structure is the same across login, signup, checkout and content-view use cases. The application decides what action to take based on the verdict; Foil does not auto-block.
For the use-case contexts where this API is applied, account takeover prevention, payment fraud detection, ecommerce fraud prevention, fake account prevention, and bot detection.
Further reading
- Sift, Fraud Detection Service Integration Docs: developers.sift.com
- IPQS, Fraud Prevention API documentation: ipqualityscore.com/documentation/overview
- SEON, Fraud Detection API: How It Works & Key Benefits: seon.io/resources
- Transactionlink, Top 13 Fraud Detection APIs for Digital Onboarding: transactionlink.io/blog