Skip to main content

Using Risk Scores Responsibly

The business problem​

risk_score, risk_level, and AML status are the fields most likely to be wired directly into an automated accept/deny decision, because they look like a single number a rule engine can branch on. That's exactly the wrong way to use them: every one is a probabilistic signal built from imperfect inputs, not a verdict. This guide covers how to read them correctly — where the signal actually comes from, how uncertain it is, what to do about false positives, and two hard lines: adverse-action restrictions and prohibited inference from location data.

What AfriHex returns​

Every risk signal traces back to one of three sources, and knowing which one you're looking at changes how much to trust it:

SourceEndpointWhat backs it
Fraud signalsPOST /v2/kyc/verify, POST /v2/verify/proximity (fraud_check block)Device/network heuristics — IP-geo match, VPN detection, velocity, mock-location
Portfolio riskPOST /v2/analytics/portfolio-riskYour own submitted loan performance data, bucketed geographically
AML/PEP screeningPOST /v2/aml/screenName-matching against a sanctions/PEP dataset — see the coverage caveat below

Before you begin​

  • Read Understanding Confidence and Verification first — it's the same evidence-not-proof principle, applied narrowly to location claims. This guide extends it to risk and fraud scoring specifically.
  • Have a human-review path before you wire any of these into production — retrofitting one after an automated decision has already caused harm is the wrong order.
  • Know your regulatory context. "Adverse action" has a specific legal meaning in many jurisdictions' credit and lending law — if you're using these signals to deny credit, service, or employment, get your own legal review of what notice/explanation obligations apply; this guide is not that review.

Copyable request​

curl -X POST "https://api.afrihex.com/v2/kyc/verify" \
-H "X-API-Key: $AFRIHEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cust_123",
"method": "gps_fix",
"location": { "lat": 5.6041, "lng": -0.1953 },
"consent_token": "tok_..."
}'

Full example response​

{
"fraud_check": {
"blocked": false,
"risk_score": 0.12,
"risk_level": "low",
"signals": [
{ "type": "ip_geo_match", "severity": "low", "description": "IP country matches address country", "score": 0.05 }
],
"recommendation": "Approve transaction",
"degraded": false,
"degraded_checks": []
}
}

Field-by-field explanation: signal provenance and uncertainty​

  • signals[].type — the specific heuristic that fired (ip_geo_match, VPN/proxy detection, velocity abuse, mock location). Read the individual signals, not just the rolled-up risk_score — two verifications with the same score can be flagged for entirely different reasons, needing different follow-up.
  • degraded: true — one or more sub-checks (velocity, IP-bulk, device-bulk, synthetic-identity) couldn't run, so risk_score reflects fewer signals than usual. This fails open by design. Read it as "we don't fully know," never as "nothing suspicious" — treat a degraded high-stakes verification as incomplete evidence, not a clean pass.
  • recommendation — advisory text, not an instruction your system is obligated to follow automatically. It's an input to your policy, phrased as a suggestion because it is one.
  • AML status: "clear" — means no match against the sanctions index that's currently loaded. The PEP/politically-exposed-persons collection isn't fully loaded yet (see Risk & Fraud's AML/PEP warning) — a clear result is a work-in-progress signal, not a completed compliance check.

False positives and human review​

Every signal here has a real false-positive rate — a legitimate customer on a shared/carrier-grade IP can trip ip_geo_match; a family member genuinely re-verifying a shared address trips previously_verified_by_others; a customer on a work VPN trips proxy detection. Concretely:

  • POST /v2/aml/screenings/{id}/review exists for exactly this reason — record a decision of clear or hit with notes, so a name-match against a sanctions list becomes a documented human determination, not a silent auto-block. A top_score above your threshold routes to this queue; it should never itself be the denial.
  • POST /v2/admin/fraud/alerts/{id}/status — mark a fraud alert confirmed or false_positive. Track your false-positive rate per signal type over time; a signal with a high false-positive rate in your population is telling you to raise its threshold or drop it from your policy, not something to route around silently.
  • blocked: true is reserved for critical signals only — everything below that is advisory by design. Building your own auto-block on a medium or high (non-blocked) result overrides a deliberate design choice to leave that decision to you.

Adverse-action restrictions​

If a risk or fraud signal contributes to denying credit, an account, a service, or an employment decision, treat it the same way you'd treat a credit-bureau score: with a documented policy, a verification_id/ screening_id/alert ID tying the decision to specific evidence, and — where your jurisdiction's law requires it — a process for giving the affected person a reason and a path to dispute it. Nothing in this API is a substitute for that legal process; it's the evidence the process runs on.

Prohibited inference from location data​

Location history — pings, hotspots, hex-cell visit patterns — can, in principle, be used to infer things far beyond "did this address check out": religious observance (regular visits to a place of worship), health status (a clinic or treatment center), political activity (a rally or party office), or relationships (a residence that isn't the declared one). None of this is computed or labelled by AfriHex, and none of it is authorized by background_location consent captured for a stated purpose like loan_collections. Using a customer's location trail to infer any of these — even when the raw data is technically available to you — is outside the scope of what the customer agreed to and is the kind of sensitive-inference use that data-protection law in most jurisdictions treats as its own violation, independent of consent for the underlying collection.

The same caution applies at the portfolio level: POST /v2/analytics/portfolio-risk buckets loan performance geographically to help you triage collections effort on your existing book — it is not a basis for inferring anything about people who live in a zone but haven't transacted with you, which is the geographic-proxy-discrimination (redlining) risk covered in Risk & Fraud's compliance notes.

Error and edge cases​

  • A degraded fraud check on a high-value transaction. Don't average a degraded score with a full one from a retry — treat the transaction as under-evidenced and escalate, rather than silently accepting whichever result came back.
  • Two customers score identically but for different signal combinations. Don't build UI or policy around a single number when the underlying signals[] array is what actually explains the score — show or log the signals, not just the rollup.
  • A false_positive-marked alert recurs for the same customer. That's a signal your threshold or rule is miscalibrated for this population, not that the customer is now "extra suspicious" for having been flagged twice.

Production checklist​

  • No automated deny/block decision runs on a degraded result without route-to-human-review.
  • Every signal type's false-positive rate is tracked over time (confirmed vs. false_positive on fraud alerts, reviewed_clear vs. reviewed_hit on AML screenings), and thresholds are revisited when it drifts.
  • Adverse decisions carry a documented reason and a reference ID, and your legal/compliance team has signed off on the notice process your jurisdiction requires.
  • No location-history query in your codebase infers religion, health, political affiliation, or undisclosed relationships from ping/hotspot data — audit for this explicitly, since nothing in the API itself will stop you from building it.
  • Portfolio-risk geographic buckets are used for collections triage on your existing book only, never as an input to underwriting a new applicant.