Fraud prevention
Stop fake accounts, card testing and abuse with one API. openshield runs openfraud, which is open source, for you under /fraud/v1. Call decide before a sign-up, a login or a payment, and get allow, step_up, review, hold, block or quarantine with the reasons. Call events to report what happened.
It scores IP data (datacenter, VPN, Tor, ASN), disposable email domains, phone numbers, device and browser signals, velocity counters and linked accounts. Every rule shows its reasons, and new rules start in shadow before they act.
Your organization gets an instance of its own: its events, accounts and cases never mix with anyone else's. It runs on our server in Germany, and your events, decisions and cases are kept 90 days. The fraud API is in beta.
Turn it on
An owner or an admin turns it on in the dashboard, under Fraud prevention. The instance starts in a few seconds. Then create an API key with fraud access (API keys, then Create):
| Fraud access | Scope | What the key may call |
|---|---|---|
| Decide and report events only | fraud_events | decide, events, events/batch, appeals and statements |
| All of it | fraud | every route below, cases, accounts, lists and erasure included |
A backend that only reports sign-ups and payments needs no more than fraud_events. Keep fraud keys for your back office.
Decide before you act
Call decide before a sensitive action (a sign-up, a login, a payment, a message sent to many people) and act on its answer.
curl -X POST "https://api.openshield.dev/fraud/v1/decide" \
-H "Authorization: Bearer $OPENSHIELD_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "account.created",
"actor_id": "u_123",
"email": "someone@example.com",
"ip": "203.0.113.50",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",
"language": "fr-BE,fr;q=0.9",
"country": "BE",
"device": "dev_8f2c41"
}'
openfraud's SDKs work as they are: point them at https://api.openshield.dev/fraud.
import { Client } from "openfraud"
const fraud = new Client({ baseUrl: "https://api.openshield.dev/fraud", apiKey: process.env.OPENSHIELD_KEY, timeoutMs: 800 })
const d = await fraud.decide({ type: "account.created", actor_id: user.id, email, ip, user_agent, language, country, device })
if (d.decision === "block" || d.decision === "quarantine") return refuse()
if (d.decision === "step_up") return askForProof()
The SDKs fail open: on a timeout, a 429 or a 5xx they retry, then answer allow. Give them at least 800 ms: a call goes through openshield's edge before it reaches your instance.
The answer has the decision (allow, step_up, review, hold, block or quarantine), the rules that fired and why, the actions to apply, and the case it opened, if any. openfraud's API reference describes every field.
Send the end user's context, not your server's
The gateway passes your request body to your instance untouched. It never fills these fields from its own request: that request comes from your backend, and its address, User-Agent and language describe your server.
| Field | What to send |
|---|---|
ip | The end user's address. Behind a proxy, the first X-Forwarded-For entry you trust. |
user_agent | The browser's User-Agent header. Your backend's own (node, python-requests) makes every event look scripted. |
language | The browser's Accept-Language header. |
country | From your CDN: CF-IPCountry, CloudFront-Viewer-Country. |
device | The id from openfraud's device script, and attrs.webdriver from its automated(). |
email, phone, ip and device are replaced by keyed pseudonyms before any rule sees them, and the raw values are not stored. The User-Agent is read for the browser, the system and the signs of automation, then dropped.
Report what happened
POST /fraud/v1/events reports an event after the fact (a payment that went through, a refund, a dispute) without waiting for a decision. POST /fraud/v1/events/batch takes up to 100 at once. Rules learn from these: a card used by many accounts, a trial taken again and again.
Prices
| Call | Price |
|---|---|
Decision (/fraud/v1/decide) | EUR 0.001 |
Event (/fraud/v1/events, each accepted event of a batch) | EUR 0.0002 |
They come from the same prepaid balance as the checks, the monthly free use included. A call that does not end in a 2xx costs nothing. The other routes (cases, accounts, lists) are free.
What your instance checks
The core always runs: blocklists you fill, trust earned by paying, and account takeover signals. In the dashboard you add rule packs for the kind of abuse you see: free tiers and trials, cards and payments, messaging, marketplaces and payouts, APIs and compute, hosting and content. Every pack rule starts in shadow: it says what it would do, in shadow, without doing it, until it is promoted to act. When rules start to act says how.
The network data (the operator and country of each address, and the lists of disposable email domains, Tor exits, data centres and VPNs) comes with your first top-up. openshield refreshes it once a day, and your instance downloads it from inside the node: it reaches nothing on the internet.
When rules start to act
- Day 1. The core blocklists act from the first call. Every other rule starts in shadow: the answer lists what it would have done under
shadow, and nothing is blocked because of it. - Automatic promotion, on by default. Every day after the first 30, openshield looks at each shadow rule that hit at least 20 accounts, and promotes it to act when it has proved reliable. Either you labelled the accounts it hit, with a precision of at least 95 % over at least 10 labelled accounts, or our decision model reviewed pseudonymized timelines of up to 10 accounts the rule hit and found at least 90 % of them fraudulent.
- What a promoted rule may do. Whatever the rule is built for, a block included.
- Manual mode. You can switch to manual promotion, so that a rule acts only when a person promotes it. Or you can keep automatic promotion but limit it to rules whose action is hold or review, so that a rule that blocks is promoted by a person only.
- You are told. Every automatic promotion is emailed to you and recorded in the audit log.
- A brake. A brake stops the acting rules when a spike of decisions shows up.
- Back to shadow. A promoted rule whose labelled precision falls under 80 % goes back to shadow on its own.
Labelling the accounts you know to be fraud or legitimate (POST /fraud/v1/actors/{id}/label) is what makes promotion reliable, and what demotes a rule that has gone wrong.
Webhooks
Set a webhook address in the dashboard. Every decision other than allow, and every change to a case, an account or a workspace, is posted to it, signed with the Standard Webhooks scheme under the secret the dashboard shows. Any Standard Webhooks library checks them, as does the SDK:
import { verifyWebhook } from "openfraud"
const event = await verifyWebhook(process.env.OPENSHIELD_FRAUD_WEBHOOK_SECRET, req.headers, rawBody)
Answer with a 2xx within 10 seconds. A webhook that is not delivered is sent again for about two days, under the same webhook-id; a 410 stops it.
A paused instance
A free organization's instance is paused after 30 days without a call: its data is kept. The next call wakes it and gets 503 fraud_waking with Retry-After: 5, and the SDKs answer allow meanwhile. An owner or an admin can also resume it from the dashboard.
Errors
These answers come from openshield; every other one is your instance's own, in openfraud's envelope.
| Status | Code | When |
|---|---|---|
401 | unauthorized | no key, or a wrong one |
403 | forbidden | the key has no fraud access, or only fraud_events for a route that needs fraud |
404 | fraud_not_enabled | your organization has not turned fraud prevention on |
429 | quota_exceeded | the balance is used up |
502 | fraud_unavailable | your instance did not answer: retry in a few seconds |
503 | fraud_waking | your instance was paused and is starting: retry after Retry-After |