Risk engine

Before any provider sees a card-family payment, Finscale scores it. Most payments pass in milliseconds; the risky few are held for review or refused outright — before an authorization ever happens. This page explains the risk object, the three decisions, and the signals behind the score.

Every card payment is screened

Risk screening runs inside POST /v1/payments, after validation and before routing. It always runs for the card family — card, apple_pay, google_pay. Methods where the customer authenticates at their own bank (bank redirects, real-time payments) are not risk-scored, and their risk field is null.

POST /v1/payments risk screening score 0–99 approved routing provider attempts review processing — held → payment.risk_review blocked failed · payment_blocked no provider attempted approve block

Three decisions, three behaviors:

risk.decisionWhat happensWhat you see
approved The payment proceeds to routing and providers, uninterrupted. The overwhelming majority of traffic. Normal lifecycle — processingsucceeded or failed.
review The payment holds in processing. No provider is attempted until an operator approves or blocks it in the dashboard. payment.risk_review fires; the payment stays processing until resolved.
blocked The payment is refused. No provider is attempted, no authorization happens, the customer is never charged. status: failed with failure_code: payment_blocked.

The risk object

Once screening runs, the assessment is attached to the payment as risk — on the API response, on every retrieve, and in every webhook payload:

payment.risk — an approved card payment
"risk": {
  "score": 17,
  "decision": "approved",
  "checks": {
    "avs_result": "pass",
    "cvv_result": "pass"
  }
}
FieldTypeMeaning
scoreinteger, 0–99Composite risk score — 0 is lowest risk, 99 highest. Deterministic in test mode.
decisionenumapproved · review · blocked — what screening decided (table above).
checks.avs_resultenumpass · fail · unavailable — Address Verification System result.
checks.cvv_resultenumpass · fail · unavailable — card verification code result.

risk is null for methods that are not risk-scored — an ideal or pix payment carries "risk": null, because the customer authenticated at their bank and the fraud model belongs to that rail.

Score bands

The score maps to a decision through thresholds. These are the platform defaults — both cut-offs are tunable per account from the dashboard, so a marketplace selling gift cards can run tighter than a subscription business:

ScoreBandDefault outcome
0–39lowapproved
40–79elevatedapproved — but a hard signal (velocity breach, country restriction) can still escalate to review
80–89highreview — held for a human decision
90–99criticalblocked

Tuning is a trade: lower the review threshold and you catch more fraud but slow more good customers. Watch two dashboard numbers when you move it — the review queue's false-positive rate, and your dispute rate. Disputes are the lagging truth about where the threshold should sit.

The review flow

A review decision holds the payment in processing and fires payment.risk_review. Nothing has been authorized yet — the hold happens before any provider attempt, so approving later runs a fresh, normal authorization. In test mode, the magic amount 4991 forces this flow:

POST /v1/payments — amount 4991 forces a review hold
curl https://api.finscale.dev/v1/payments \
  -H "Authorization: Bearer sk_test_51FinscaleDemo…" \
  -H "Idempotency-Key: idem_risk_review_demo_01" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 4991,
    "currency": "EUR",
    "payment_method": "card",
    "payment_method_id": "pm_7Wq2xN9dT4Ls",
    "reference": "ord_9f21_0716"
  }'
201 Created — held in review, no provider attempted
{
  "id": "pay_7Kd2nQ5wR8Xm",
  "object": "payment",
  "amount": 4991,
  "currency": "EUR",
  "status": "processing",
  "payment_method": "card",
  "payment_method_id": "pm_7Wq2xN9dT4Ls",
  "reference": "ord_9f21_0716",
  "provider": null,
  "provider_attempts": [],
  "risk": {
    "score": 82,
    "decision": "review",
    "checks": { "avs_result": "pass", "cvv_result": "pass" }
  },
  "created_at": "2026-07-16T11:08:44Z",
  "livemode": false
}

Resolve the hold in the dashboard's review queue:

  1. Approve

    The payment leaves the hold and enters routing as if nothing happened — provider attempts run, and payment.succeeded or payment.failed follows as usual.

  2. Block

    The payment moves to failed with failure_code: payment_blocked, and payment.failed fires. The customer was never charged.

Design your checkout for the hold A payment in review looks like any other processing payment to your code. Don't spin on the status — show your normal "payment in progress" state, listen for the webhook that resolves it, and confirm the order asynchronously. Reviews are resolved by humans; minutes, not milliseconds.

The blocked flow

A blocked decision refuses the payment before any provider sees it. The create call still returns 201 — the refusal is a payment outcome, not a request error:

201 Created — blocked by risk screening (test amount 4992)
{
  "id": "pay_9Tn4xW2mK6Rd",
  "object": "payment",
  "amount": 4992,
  "currency": "EUR",
  "status": "failed",
  "provider": null,
  "provider_attempts": [],
  "risk": {
    "score": 96,
    "decision": "blocked",
    "checks": { "avs_result": "fail", "cvv_result": "fail" }
  },
  "failure_code": "payment_blocked",
  "failure_message": "The payment was blocked by risk screening.",
  "livemode": false
}

Show the customer a generic decline message. Don't surface "blocked by risk screening" at checkout — it teaches card testers exactly which of their inputs tripped the wire.

Signals behind the score

The score is a composite. These are the signal families the platform weighs — the first is visible on the payment; the rest are capabilities you configure and observe in the dashboard.

AVS & CVV verification

The two instrument checks ride on risk.checks for every screened payment:

Checkpassfailunavailable
avs_result Billing address matched the issuer's records — lowers the score. Address mismatch — raises the score, sharply when combined with other signals. The issuer or method doesn't support AVS — common outside a handful of markets. Neutral.
cvv_result Verification code matched — lowers the score. Code mismatch — a strong fraud signal; stolen numbers usually travel without the code. Not checked (wallet flows, network tokens with cryptograms). Neutral.

A fail never blocks on its own — plenty of legitimate customers typo their postcode. It moves the score; the composite decides.

Velocity rules

Velocity rules count events over sliding windows and escalate when a count breaks its limit — the signature of card testing is many attempts, small amounts, one origin. Configurable dimensions include:

  • Attempts per card — the same instrument retried across a short window.
  • Cards per device or IP — one origin cycling through many card numbers; the strongest card-testing signal.
  • Attempts per customer email — one identity, many instruments.

Each rule carries its own escalation — push the score up, force review, or blocked outright. Velocity breaches are the usual reason an otherwise mid-score payment lands in the review queue.

BIN intelligence

The leading digits of a card identify the issuer, and with it the issuing country, funding type (debit, credit, prepaid), and card class. Screening cross-references them: a prepaid card, issued in one country, used from an IP in a second, shipping to a third, scores very differently from a domestic debit card behaving domestically. The derived attributes surface on the payment in payment_method_details.cardfunding, country, brand — so your own systems can use them too.

Country restrictions

Per-account allow and block lists over issuing country and customer IP country. A payment matching a blocked country goes straight to decision: blocked — no score debate, no provider attempt. Configure them in the dashboard; use them for sanctioned markets, corridors you can't ship to, or fraud hotspots you've measured. Restrictions here complement routing rules: a routing block refuses traffic you never want to process, a risk country restriction refuses traffic you consider fraudulent.

Device fingerprinting

Hosted checkout and Finscale fields collect device and browser signals automatically during entry — no work on your side. The signals feed velocity counting (cards per device) and anomaly checks (headless browsers, automation frameworks, impossible travel between attempts). A server-only API integration carries none of these signals, so card-testing detection leans harder on IP and card velocity — one more reason to take card input through Finscale-served surfaces, alongside the PCI scope argument.

Webhook events

EventWhen
payment.risk_reviewScreening held the payment for review. The payload is the full payment, risk.decision: "review", still processing.
payment.succeededAfter approval (or straight through for approved), a provider approved the payment.
payment.failedBlocked at screening, blocked in review, or declined by providers after approval — failure_code tells you which.

Handle payment.risk_review if you run time-sensitive fulfillment — it's your signal to pause the order rather than time it out. Signature verification and retry semantics are on the Webhooks page.

Error scenarios

ScenarioWhat the API returns
Payment blocked at screening 201 with status: failed, failure_code: payment_blocked, empty provider_attempts. Not an error envelope — the request was valid; the payment was refused.
Review resolved as block No new API response — payment.failed fires with failure_code: payment_blocked.
Blocked by a routing rule, not risk failure_code: blocked_by_routing_rule — your own rule matched. risk may still say approved.
Method not risk-scored "risk": null on the payment — expected for bank redirects and real-time methods, not a bug.
  • Cards & 3DS — 3-D Secure authentication and liability shift, the other half of card fraud defense.
  • Disputes — what happens when fraud gets through, and how to fight it.
  • Smart routing — where an approved payment goes next.
  • Testing — magic amounts 4991 (review) and 4992 (blocked) in test mode.