ScamVerify™
Use Cases

Fintech KYC

Use the ScamVerify™ API to verify customer phone numbers during KYC onboarding, detect VoIP fraud, and flag high-risk carriers.

Phone number verification is a critical step in Know Your Customer (KYC) workflows. The ScamVerify™ API gives fintech teams a way to assess phone number risk before account creation, catching fraudulent signups that traditional verification misses.

The Problem

Standard phone verification (send an OTP, user confirms it) only proves that someone controls a number. It tells you nothing about the number's history, carrier reputation, or association with reported fraud. Fraudsters routinely use disposable VoIP numbers that pass OTP checks but have extensive complaint histories.

How ScamVerify™ Helps

Every phone lookup returns structured data you can use to make automated onboarding decisions:

  • Line type detection identifies VoIP, mobile, and landline numbers
  • Carrier risk scoring flags the 18 high-risk VoIP carriers most associated with fraud
  • FTC complaint history reveals numbers with prior scam reports
  • Robocall detection catches numbers associated with automated calling campaigns
  • AI-synthesized verdict gives you a single risk score (0 to 100) and a clear verdict

Decision Flow

A typical KYC integration follows this pattern:

  1. Customer submits their phone number during signup
  2. Your backend calls the ScamVerify™ API
  3. Based on the response, you either approve, flag for manual review, or reject
Customer submits phone number
         |
         v
  Call ScamVerify™ API
         |
         v
   risk_score < 30? -----> APPROVE (auto-verify)
         |
         v
   risk_score < 70? -----> REVIEW (manual KYC)
         |
         v
   risk_score >= 70? ----> REJECT (block signup)

Code Example

async function verifyPhoneForKYC(phoneNumber) {
  const response = await fetch('https://scamverify.ai/api/v1/phone/lookup', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.SCAMVERIFY_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ phone_number: phoneNumber }),
  });

  if (!response.ok) {
    throw new Error(`ScamVerify API error: ${response.status}`);
  }

  const result = await response.json();

  // Decision logic based on risk assessment
  if (result.risk_score >= 70) {
    return {
      decision: 'REJECT',
      reason: result.explanation,
      signals: result.signals,
    };
  }

  if (result.risk_score >= 30) {
    return {
      decision: 'REVIEW',
      reason: result.explanation,
      flags: {
        isVoip: result.signals.line_type === 'voip',
        ftcComplaints: result.signals.ftc_complaints,
        robocallDetected: result.signals.robocall_detected,
      },
    };
  }

  return {
    decision: 'APPROVE',
    carrier: result.signals.carrier,
    lineType: result.signals.line_type,
  };
}

// Usage in your signup handler
app.post('/api/signup', async (req, res) => {
  const { email, phone } = req.body;

  const verification = await verifyPhoneForKYC(phone);

  if (verification.decision === 'REJECT') {
    return res.status(400).json({
      error: 'Phone number failed verification',
      code: 'PHONE_REJECTED',
    });
  }

  if (verification.decision === 'REVIEW') {
    // Create account but flag for manual review
    await createAccount(email, phone, { needsReview: true });
    await notifyComplianceTeam(email, phone, verification.flags);
    return res.json({ status: 'pending_review' });
  }

  // Clean number, proceed normally
  await createAccount(email, phone, { needsReview: false });
  return res.json({ status: 'approved' });
});
import requests
import os

def verify_phone_for_kyc(phone_number: str) -> dict:
    response = requests.post(
        "https://scamverify.ai/api/v1/phone/lookup",
        headers={
            "Authorization": f"Bearer {os.environ['SCAMVERIFY_API_KEY']}",
            "Content-Type": "application/json",
        },
        json={"phone_number": phone_number},
    )
    response.raise_for_status()
    result = response.json()

    if result["risk_score"] >= 70:
        return {
            "decision": "REJECT",
            "reason": result["explanation"],
            "signals": result["signals"],
        }

    if result["risk_score"] >= 30:
        return {
            "decision": "REVIEW",
            "reason": result["explanation"],
            "flags": {
                "is_voip": result["signals"]["line_type"] == "voip",
                "ftc_complaints": result["signals"]["ftc_complaints"],
                "robocall_detected": result["signals"]["robocall_detected"],
            },
        }

    return {
        "decision": "APPROVE",
        "carrier": result["signals"]["carrier"],
        "line_type": result["signals"]["line_type"],
    }

Key Signals for KYC

SignalWhat It Means for KYC
line_type: "voip"Higher fraud risk. VoIP numbers are cheap and disposable. Not disqualifying on its own, but combined with other signals it warrants review.
ftc_complaints > 0The number has been reported to the FTC. Any complaints should trigger manual review at minimum.
robocall_detected: trueThe number is associated with automated calling. Strong indicator of fraud or abuse.
carrier (high-risk)Some VoIP carriers are disproportionately used for fraud. ScamVerify™ tracks 18 high-risk carriers.
risk_score >= 70Multiple negative signals detected. Safe to auto-reject in most KYC flows.

Best Practices

Cache-friendly lookups. If the same phone number is submitted multiple times (for example, a user retrying signup), the ScamVerify™ API returns cached results at no additional quota cost.

  • Never reject on VoIP alone. Many legitimate customers use Google Voice, Skype, or other VoIP services. Combine line type with complaint history and carrier reputation.
  • Log the full response. Store the risk_score, verdict, and signals alongside the customer record for compliance audits.
  • Set thresholds based on your risk tolerance. The 30/70 split shown above is a starting point. Adjust based on your fraud rates.
  • Use test keys during development. sv_test_ keys return realistic mock data without consuming quota.

On this page