ScamVerify™
Use Cases

Dating App Safety

Protect users from romance scams, catfishing, and phishing links in dating platforms.

Romance scams cost Americans over $1.3 billion annually, making them the most financially devastating form of consumer fraud. Dating platforms are the primary hunting ground. Scammers create fake profiles, build emotional connections, then extract money through fake emergencies, investment schemes, or phishing links. The ScamVerify™ API helps platforms detect these threats at the moment they escalate.

Where Scams Happen on Dating Platforms

Profile Verification Gaps

Scammers register with disposable VoIP numbers that pass basic OTP verification. They cycle through numbers as accounts get banned, creating new profiles within minutes.

Phone Number Exchange

The critical escalation point. When a scammer convinces a victim to move off-platform and share phone numbers, the platform loses visibility. Scanning shared numbers in messages catches known scam numbers before victims make contact.

Phishing URLs disguised as "photo albums," "video calls," or "verification links" are the most common attack vector in dating app DMs. These lead to credential harvesting pages, malware downloads, or fake identity verification sites that steal personal information.

How ScamVerify™ Helps

Integrate ScamVerify™ at three points in the user journey:

  1. Registration: Check phone numbers at signup to block disposable VoIP numbers
  2. Message scanning: Detect phone numbers and URLs shared in conversations
  3. Report verification: When users report suspicious profiles, verify the reported contact details

Code Example: Message Content Scanning

class DatingAppSafetyScanner {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://scamverify.ai/api/v1';
  }

  async callApi(endpoint, body) {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
    });
    if (!response.ok) throw new Error(`API error: ${response.status}`);
    return response.json();
  }

  async scanMessage(messageText, senderId) {
    const threats = [];

    // Extract and check phone numbers
    const phoneRegex = /\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g;
    const phones = messageText.match(phoneRegex) || [];

    for (const phone of phones) {
      const result = await this.callApi('/phone/lookup', {
        phone_number: phone,
      });

      if (result.risk_score >= 40) {
        threats.push({
          type: 'risky_phone',
          value: phone,
          riskScore: result.risk_score,
          reason: result.explanation,
          lineType: result.signals.line_type,
        });
      }
    }

    // Extract and check URLs
    const urlRegex = /https?:\/\/[^\s)]+/gi;
    const urls = messageText.match(urlRegex) || [];

    for (const url of urls) {
      const result = await this.callApi('/url/lookup', { url });

      if (result.risk_score >= 30) {
        threats.push({
          type: 'risky_url',
          value: url,
          riskScore: result.risk_score,
          reason: result.explanation,
          verdict: result.verdict,
        });
      }
    }

    return {
      safe: threats.length === 0,
      threats,
      action: this.determineAction(threats, senderId),
    };
  }

  determineAction(threats, senderId) {
    if (threats.length === 0) return 'ALLOW';

    const hasHighRisk = threats.some(t => t.riskScore >= 70);
    const hasPhishing = threats.some(
      t => t.type === 'risky_url' && t.verdict === 'phishing'
    );

    if (hasHighRisk || hasPhishing) {
      return 'BLOCK_AND_FLAG_ACCOUNT';
    }

    const hasMediumRisk = threats.some(t => t.riskScore >= 40);
    if (hasMediumRisk) {
      return 'BLOCK_MESSAGE_WARN_RECIPIENT';
    }

    return 'WARN_RECIPIENT';
  }
}

// Integration as message middleware
const scanner = new DatingAppSafetyScanner(process.env.SCAMVERIFY_API_KEY);

app.post('/api/messages/send', async (req, res) => {
  const { senderId, recipientId, text } = req.body;

  // Only scan messages that contain phone numbers or URLs
  const hasContactInfo = /\d{3}[-.\s]?\d{3}[-.\s]?\d{4}|https?:\/\//i.test(text);

  if (hasContactInfo) {
    const scan = await scanner.scanMessage(text, senderId);

    switch (scan.action) {
      case 'BLOCK_AND_FLAG_ACCOUNT':
        await flagAccount(senderId, {
          reason: 'Sent message with high-risk contact details',
          threats: scan.threats,
        });
        return res.status(403).json({
          error: 'This message could not be sent.',
        });

      case 'BLOCK_MESSAGE_WARN_RECIPIENT':
        await logBlockedMessage(senderId, recipientId, scan.threats);
        return res.status(400).json({
          error: 'This message contains content that may be unsafe.',
        });

      case 'WARN_RECIPIENT':
        // Send the message but add a safety warning
        await sendMessageWithWarning(senderId, recipientId, text, {
          warning: 'This message contains external links or phone numbers. Be cautious.',
        });
        return res.json({ status: 'sent_with_warning' });
    }
  }

  // No contact info detected, send normally
  await sendMessage(senderId, recipientId, text);
  return res.json({ status: 'sent' });
});
import re
import requests
import os

class DatingAppSafetyScanner:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://scamverify.ai/api/v1"

    def _call_api(self, endpoint: str, body: dict) -> dict:
        response = requests.post(
            f"{self.base_url}{endpoint}",
            headers={
                "Authorization": f"Bearer {self.api_key}",
                "Content-Type": "application/json",
            },
            json=body,
        )
        response.raise_for_status()
        return response.json()

    def scan_message(self, message_text: str) -> dict:
        threats = []

        # Check phone numbers
        phones = re.findall(r"\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}", message_text)
        for phone in phones:
            result = self._call_api("/phone/lookup", {"phone_number": phone})
            if result["risk_score"] >= 40:
                threats.append({
                    "type": "risky_phone",
                    "value": phone,
                    "risk_score": result["risk_score"],
                    "line_type": result["signals"]["line_type"],
                })

        # Check URLs
        urls = re.findall(r"https?://[^\s)]+", message_text, re.IGNORECASE)
        for url in urls:
            result = self._call_api("/url/lookup", {"url": url})
            if result["risk_score"] >= 30:
                threats.append({
                    "type": "risky_url",
                    "value": url,
                    "risk_score": result["risk_score"],
                    "verdict": result["verdict"],
                })

        return {
            "safe": len(threats) == 0,
            "threats": threats,
        }

Threat Signal Matrix for Dating Platforms

ScenarioSignals to CheckRisk LevelAction
New user registers with VoIP numberline_type: "voip" + high-risk carrierMediumRequire photo verification before messaging
User shares phone number in DMPhone risk_score >= 50HighBlock message, warn recipient
User sends "verification" linkURL verdict = phishingCriticalBlock message, suspend sender
User sends shortened URLURL risk_score >= 30MediumShow safety interstitial to recipient
Reported profile's phone has FTC complaintsftc_complaints > 0HighSuspend account pending investigation
User sends link to unknown domainURL with no threat intel matchLowAllow but display safety reminder

Registration Screening

Block the most obvious fake accounts at signup before they waste your moderation team's time.

async function screenDatingSignup(phoneNumber) {
  const result = await scanner.callApi('/phone/lookup', {
    phone_number: phoneNumber,
  });

  // Romance scammers overwhelmingly use disposable VoIP
  if (result.signals.line_type === 'voip' && result.risk_score >= 30) {
    return {
      approved: false,
      reason: 'additional_verification_required',
      // Require selfie or video verification instead of rejecting outright
      nextStep: 'photo_verification',
    };
  }

  if (result.risk_score >= 60) {
    return {
      approved: false,
      reason: 'phone_not_accepted',
    };
  }

  return { approved: true };
}

Privacy-first scanning. The ScamVerify™ API checks phone numbers and URLs against threat intelligence databases. It does not read, store, or analyze the personal content of messages. You control what gets sent to the API and when.

Handling User Reports

When users report suspicious profiles, use the API to verify the reported details and build a case.

async function processUserReport(reportedUserId, reportDetails) {
  const reportedUser = await getUser(reportedUserId);
  const enrichment = {};

  // Verify the reported user's registration phone
  const phoneCheck = await scanner.callApi('/phone/lookup', {
    phone_number: reportedUser.phone,
  });
  enrichment.phoneRisk = phoneCheck.risk_score;
  enrichment.phoneVerdict = phoneCheck.verdict;
  enrichment.lineType = phoneCheck.signals.line_type;

  // If the report includes URLs the scammer shared
  if (reportDetails.sharedUrls) {
    enrichment.urlChecks = [];
    for (const url of reportDetails.sharedUrls) {
      const urlCheck = await scanner.callApi('/url/lookup', { url });
      enrichment.urlChecks.push({
        url,
        riskScore: urlCheck.risk_score,
        verdict: urlCheck.verdict,
      });
    }
  }

  // Auto-suspend if evidence is strong
  const shouldAutoSuspend =
    phoneCheck.risk_score >= 60 ||
    enrichment.urlChecks?.some(u => u.riskScore >= 50);

  if (shouldAutoSuspend) {
    await suspendAccount(reportedUserId, {
      reason: 'automated_scam_detection',
      evidence: enrichment,
    });
  }

  return enrichment;
}

Best Practices

  • Scan at the escalation point. The highest-value moment is when users share contact details in messages. That is where romance scams transition from social engineering to financial exploitation.
  • Do not over-block. False positives destroy user trust on dating platforms. Use warnings and interstitials for medium-risk signals. Reserve hard blocks for confirmed phishing and high-risk numbers.
  • Combine with behavioral signals. A new account that messages 50 people in 24 hours and shares the same URL with all of them is suspicious regardless of what ScamVerify™ returns. Use API signals alongside your own behavioral analytics.
  • Respect user privacy. Only scan messages that contain phone numbers or URLs. Do not send entire conversation histories to any external API.

Getting Started

  1. Create an API key and set up your account
  2. Follow the Quickstart guide to make your first lookup
  3. Review the Phone Lookup API and URL Verification API reference docs
  4. Start with message scanning middleware, then expand to registration and report verification

On this page