<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fingerprint</title>
    <description>The latest articles on DEV Community by Fingerprint (@fingerprintjs).</description>
    <link>https://dev.to/fingerprintjs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F9407%2Fabee9834-dfce-4429-a191-6c39f1585071.png</url>
      <title>DEV Community: Fingerprint</title>
      <link>https://dev.to/fingerprintjs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fingerprintjs"/>
    <language>en</language>
    <item>
      <title>Vibe coding security checklist</title>
      <dc:creator>Keshia Rose</dc:creator>
      <pubDate>Tue, 18 Nov 2025 23:50:41 +0000</pubDate>
      <link>https://dev.to/fingerprintjs/vibe-coding-security-checklist-5725</link>
      <guid>https://dev.to/fingerprintjs/vibe-coding-security-checklist-5725</guid>
      <description>&lt;h2&gt;
  
  
  The rise of vibe coding in 2025
&lt;/h2&gt;

&lt;p&gt;Vibe coding is all the rage lately. If you’re (somehow) unfamiliar with the term, it’s the act of using an AI tool to code and build an application for you. Basically, anyone with an idea can type out a few prompts and spin up an app, launch a product, or start a side hustle without needing to write any code themselves. &lt;/p&gt;

&lt;p&gt;These tools are fast, fun, and seem almost magical as you watch your ideas come to life on the screen. But here’s the thing: most vibe coders aren’t developers or even technical. And while this new technology breaks down barriers and makes app building something anyone can do, it also means the apps they generate don’t necessarily follow the best security practices. When you let AI take the wheel, it tends to skip over the boring (but essential) parts like security, leaving plenty of gaps just waiting to be exploited by folks up to no good.&lt;/p&gt;

&lt;p&gt;And this isn’t just hypothetical. There are numerous examples out there of people having their vibe-coded apps hacked:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvo0petmczx5nji929gjz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvo0petmczx5nji929gjz.png" alt="twitter screenshot of vibe-coded saas hacked" width="766" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc683y7b9z0aot8ulql7e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc683y7b9z0aot8ulql7e.png" alt="twitter screenshot of vibe-coded app hacked" width="766" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or creating something they don’t know how to fix:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foh0mbdm8th62fed267vf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foh0mbdm8th62fed267vf.png" alt="formatting help reddit screenshot" width="766" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Those are the folks I’m hoping to help with this checklist. The people who want to take advantage of these amazing new technologies, but still build safely. Use this list as a starting point to confirm (or at least ask your AI tool to double-check) some security essentials before you hit “deploy” on the app you just vibe-coded into existence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden security risks of vibe coding
&lt;/h2&gt;

&lt;p&gt;Security holes in AI-built apps are more common than most people realize. Take the Tea app, for example. The team left an entire &lt;a href="https://www.bitdefender.com/en-us/blog/hotforsecurity/major-security-breach-at-tea-app-exposes-sensitive-user-data" rel="noopener noreferrer"&gt;cloud image bucket open to the internet&lt;/a&gt;, leaking tens of thousands of personal photos and ID documents. Though it hasn’t been confirmed to be a vibe-coded app, it sure smells like it. That kind of “oops” moment isn’t the result of some elite hacker attack; it’s what happens when security gets left out of the conversation entirely.&lt;/p&gt;

&lt;p&gt;The numbers make it even clearer. According to a recent report, nearly seven in ten developers have discovered &lt;a href="https://www.rg-cs.co.uk/ai-generated-code-blamed-for-1-in-5-breaches" rel="noopener noreferrer"&gt;vulnerabilities introduced by AI-generated code&lt;/a&gt;, and one in five reported that these flaws led to serious incidents with real business impact. That’s not just a few bugs here or there; that’s data leaks, service outages, and revenue loss. And even if AI doesn’t burn the house down, it still creates a mess. Sixty-six percent of developers say they now spend more time &lt;a href="https://stackoverflow.blog/2025/07/29/developers-remain-willing-but-reluctant-to-use-ai-the-2025-developer-survey-results-are-here/" rel="noopener noreferrer"&gt;fixing “almost-right” AI-generated code&lt;/a&gt; than writing their own.&lt;/p&gt;

&lt;p&gt;Vibe coding makes it easy to ship fast, but just as easy to ship something fragile. Without understanding what’s safe, compliant, or potentially exposing user data, you could end up building a liability instead of a successful product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The vibe coding security checklist
&lt;/h2&gt;

&lt;p&gt;All right, now for the part that saves you from waking up to an “uh oh” email from your hosting provider. This checklist isn’t meant to turn you into a security engineer, but it’ll help you catch some common mistakes before you launch. You should confirm that each one is being handled appropriately. Either verify it yourself or ask your AI tool to explain how it’s being done in a way that allows you to inspect it. If the AI can’t show you, it probably didn’t do it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data handling
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Validate all inputs on the backend to block malformed or malicious data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anything a user can type, upload, or submit should be treated as if it’s trying to break your app. Start by validating inputs. Every form, query, and request needs clear rules for what’s allowed. If you expect an email address, check that it’s actually an email address. If you expect a number, reject anything else. Don’t rely only on front-end validation; make sure your backend enforces it too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Restrict file types and sizes, and store uploads outside your main app directory.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;File uploads are another common weak spot. Only allow certain file types, set a maximum file size, and store uploads outside your main app directory. Never trust file names or paths provided by users; they’re easy to manipulate and can expose sensitive parts of your system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use parameterized queries to prevent SQL injection attacks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, check how your app talks to its database. SQL injection is one of the oldest and easiest attacks to pull off. It happens when someone sneaks database commands into an input field to trick your app into running them. Make sure your AI-generated code uses parameterized queries or prepared statements instead of string concatenation.&lt;/p&gt;

&lt;p&gt;These checks stop attackers from injecting SQL or scripts, prevent bad data from crashing your app, and block oversized or malicious uploads that could eat up resources or expose user data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Escape HTML in user content to stop cross-site scripting (XSS).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finally, sanitize your output. When your app displays user-generated content, escape HTML characters to prevent cross-site scripting (XSS). XSS happens when an attacker injects malicious scripts into pages viewed by other users, allowing them to steal cookies, perform actions on behalf of others, or display fake content. If your AI built a template or component that prints user input directly into the page, make sure it escapes that content properly. One unchecked field is all it takes to compromise your users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication and session security
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use strong authentication with salted password hashing and optional MFA.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Authentication is hard, and you probably shouldn’t try to roll your own. Always use a reputable library or service for handling credentials. If you insist on doing it yourself, make sure any stored passwords are hashed and salted. Hashing turns a password into a one-way value that can’t be reversed, and salting adds randomness to protect against rainbow table attacks, where precomputed lists are used to crack hashed passwords. If your AI tool wrote your auth system, confirm it isn’t storing passwords in plain text. If possible, add multi-factor authentication (MFA), since it’s one of the easiest ways to &lt;a href="https://fingerprint.com/blog/account-takeover-solutions/" rel="noopener noreferrer"&gt;prevent account takeovers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enforce access control checks on every route, not just at login.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, apply access controls beyond the login page. Authentication confirms who someone is, while authorization determines what they can do. Every sensitive route or admin action should verify that the user has permission to access it. Without these checks, anyone who’s logged in (or worse, anyone who can guess a URL) could access data they shouldn’t.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mark cookies as HttpOnly, Secure, and SameSite to protect sessions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Also, make sure to protect your sessions with secure cookies. Set cookies to “HttpOnly” so JavaScript can’t read them, “Secure” so they’re only sent over HTTPS, and “SameSite” to reduce cross-site request forgery (CSRF) risks. Skipping these flags leaves your sessions vulnerable to hijacking or unauthorized use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use device intelligence to add an additional layer of protection.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In addition to these steps, using Fingerprint adds another layer of &lt;a href="https://fingerprint.com/blog/closing-identity-security-gaps/" rel="noopener noreferrer"&gt;protection to your authentication system&lt;/a&gt;. Each device gets a &lt;a href="https://fingerprint.com/products/identification/" rel="noopener noreferrer"&gt;unique visitor ID&lt;/a&gt;, helping you recognize returning users even if they clear cookies or switch browsers. &lt;a href="https://fingerprint.com/products/smart-signals/" rel="noopener noreferrer"&gt;Smart Signals&lt;/a&gt; provide extra context by detecting things like automation, virtual machines, or mismatched network details. These signals can help you block suspicious logins, detect session hijacking attempts, and flag risky behavior before it turns into account abuse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure and configuration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Force all traffic over HTTPS and block insecure HTTP requests.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your app’s infrastructure and configuration set the foundation for its security. Start by forcing all traffic over HTTPS. HTTP sends data in plain text, which means anyone on the same network can intercept or alter requests. Redirect every HTTP request to HTTPS and make sure your SSL certificate is valid and renews automatically. Thankfully, many hosting providers do this for you, though you may need to change settings to ensure it’s always enforced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Store secrets in environment variables or a secrets manager, never in code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Store secrets like API keys, database passwords, and access tokens in a safe location and never commit them to source control like GitHub. They should live in environment variables or a secrets manager, never hard-coded in your codebase. If your AI tool generated your project, check that it didn’t sneak credentials into a random config file that’s being committed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Require authentication and proper CORS settings for all API endpoints.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Extending a bit on the authentication steps mentioned above, also secure your APIs. Every endpoint should require authentication and define strict CORS settings. CORS settings control which external domains are allowed to interact with your API and what types of requests they can make. Avoid using a wildcard “*” origin in production and limit access to trusted domains only. This prevents data exposure through unapproved clients or cross-site attacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regularly update dependencies and remove unused or vulnerable packages.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Outdated or abandoned packages are one of the fastest ways to get compromised. Update regularly, run security audits, and remove anything you’re not using. According to a report by Endor Labs, &lt;a href="https://www.endorlabs.com/lp/state-of-dependency-management-2025" rel="noopener noreferrer"&gt;49% of the dependencies&lt;/a&gt; added by AI coding agents contain known vulnerabilities. Even a single stale dependency can open a hole in your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  App behavior and resilience
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Add rate limiting to prevent abuse and brute-force attacks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add rate limiting to protect your endpoints from &lt;a href="https://fingerprint.com/blog/brute-force-attack-prevention/" rel="noopener noreferrer"&gt;brute-force logins&lt;/a&gt;, &lt;a href="https://fingerprint.com/blog/stop-credential-stuffing/" rel="noopener noreferrer"&gt;credential stuffing&lt;/a&gt;, or denial-of-service attempts. Set reasonable thresholds based on user actions, like login attempts, form submissions, or API calls, and respond with temporary blocks or delays when limits are hit. To go a step further, use device intelligence tools like Fingerprint to accurately identify repeat abusers even if they change IPs, clear cookies, or switch browsers. Combining rate limits with device-level identification helps you block persistent attackers without disrupting legitimate users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log only what’s necessary and never include secrets, tokens, or personal data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When something breaks, don’t hand attackers extra information. Show users generic error messages like “Something went wrong” and keep detailed stack traces or database errors in your private logs. Those details can reveal technologies, file paths, or query structures that make an attacker’s job easier. At the same time, log only what’s necessary to debug or investigate issues. Never record passwords, tokens, or personal data, and avoid dumping entire request bodies. Assume your logs could one day be seen by someone else, and write them accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good vibes only
&lt;/h2&gt;

&lt;p&gt;Vibe coding is one of the coolest ways to bring ideas to life fast. But moving fast shouldn’t mean skipping the basics of security. A little awareness about input validation, authentication, API security, and session handling goes a long way toward keeping your app and your users safe. &lt;/p&gt;

&lt;p&gt;The checklist in this guide gives you the essentials to confirm or ask your AI to verify before you hit deploy, so your next project ships with fewer surprises and more confidence. At the end of the day, no matter how capable your AI tool seems, you need to stay in control of what it builds and how it protects your users.&lt;/p&gt;

&lt;p&gt;If you’re ready to go beyond the basics, Fingerprint can help. Our &lt;a href="https://fingerprint.com/products/fingerprint-pro/" rel="noopener noreferrer"&gt;device intelligence platform&lt;/a&gt; adds another layer of defense with unique visitor identification and Smart Signals that catch suspicious activity early. If you want to talk about how we can make your vibe-coded app more secure, &lt;a href="https://fingerprint.com/contact-sales/" rel="noopener noreferrer"&gt;get in touch with us￼&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>programming</category>
      <category>security</category>
    </item>
    <item>
      <title>Tutorial: Outsmarting VPNs — prevent regional pricing fraud with true location detection</title>
      <dc:creator>Keshia Rose</dc:creator>
      <pubDate>Wed, 12 Mar 2025 23:32:51 +0000</pubDate>
      <link>https://dev.to/fingerprintjs/tutorial-outsmarting-vpns-prevent-regional-pricing-fraud-with-true-location-detection-24kp</link>
      <guid>https://dev.to/fingerprintjs/tutorial-outsmarting-vpns-prevent-regional-pricing-fraud-with-true-location-detection-24kp</guid>
      <description>&lt;p&gt;&lt;em&gt;Written by Keshia Rose&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When selling products or services globally, businesses often adjust pricing to match local markets, accounting for things like price sensitivity and purchasing power. It’s a win-win: businesses capture a larger market share, and customers in lower-income regions get access to goods and services they otherwise couldn’t afford. But, of course, some people just have to ruin it for everyone. Why should someone in your region pay half the price just because they flipped on a VPN and pretended to be somewhere else?&lt;/p&gt;

&lt;p&gt;Detecting a user’s true location is key to protecting your regional pricing strategy and shutting down this kind of abuse. That way, you can block fraud and keep things fair for your real customers. In this article, we’ll dive into how regional pricing fraud works, the headaches of dealing with masked locations, and how Fingerprint can help you outsmart these cheaters and keep your business protected.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is regional pricing?
&lt;/h2&gt;

&lt;p&gt;Regional pricing is the practice of charging different prices for the same goods or services based on where the customer lives. Depending on the business, “region” could mean a country, a continent, or even a shared currency. Before setting these different prices, businesses need to dive into data like currency value, average income, demand, and competition in each region.&lt;/p&gt;

&lt;p&gt;The aim is to strike a balance between affordability for customers and profitability for the business. When done right, regional pricing offers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Increased accessibility&lt;/strong&gt;: Making goods and services affordable for customers in lower-income regions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expanded market share&lt;/strong&gt;: Attracting a wider audience by tailoring prices to local conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maximized revenue&lt;/strong&gt;: Charging higher prices in affluent regions to capture greater profits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitive edge&lt;/strong&gt;: Staying aligned with local pricing expectations to compete effectively.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While regional pricing can be applied in almost any industry, it’s particularly common in subscription-based software, streaming services, gaming, travel, and e-commerce. An easy way to see this in action is on the Steam gaming platform, which allows developers to set prices by currency (e.g., Chilean Peso) and region (e.g., South Asia).&lt;/p&gt;

&lt;p&gt;For instance, as of this writing, the price of &lt;em&gt;&lt;a href="https://steamdb.info/app/1086940/" rel="noopener noreferrer"&gt;Baldur’s Gate 3&lt;/a&gt;&lt;/em&gt; on Steam ranges from $19.50 in Russia (1999 ₽) to $76.51 in Switzerland (CHF 69.99). Steam even does some of the heavy lifting, offering &lt;a href="https://partner.steamgames.com/doc/store/pricing#5" rel="noopener noreferrer"&gt;pricing guidance&lt;/a&gt; that can be especially handy for smaller shops or indie devs who don’t have the time or resources for extensive market research. This pricing makes games more accessible for lower-income regions while staying profitable for developers. However, it also attracts fraudsters looking to exploit the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  How fraudsters hide their real location
&lt;/h2&gt;

&lt;p&gt;Fraudsters are nothing if not resourceful when it comes to dodging regional pricing rules. They leverage various tools and tricks to mask their true location and access lower regional prices. Here are some of the most common methods:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Employing proxies and anonymizing networks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Proxies act as intermediaries that reroute internet traffic through a different location. Fraudsters can use them to fake their IP address and appear to be in another region. For extra anonymity, some use Tor (the onion router), which encrypts traffic within its network and routes their traffic through multiple relays, making location detection even harder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using a VPN&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Virtual Private Networks (VPNs) work similarly to proxies by routing traffic through remote servers. Unlike proxies, VPNs encrypt all traffic and operate at the system level, affecting all internet activity on a device. This adds some additional security in addition to masking their location in order to bypass restrictions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Falsifying account information&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In cases where pricing is tied to user profiles, fraudsters may simply lie about their location. They might enter fake addresses or billing details during sign-up to mimic a cheaper region. For example, someone might claim to live in India while actually residing in France.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exploiting cross-border payment methods&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some fraudsters bypass location restrictions by using payment methods associated with cheaper regions. Setting up accounts tied to international banks can allow them to appear as though they’re making purchases locally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sharing accounts across regions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Shared or resold accounts are another common tactic to access cheaper regional pricing. A fraudster might buy a subscription at a lower price in a cheaper region and then sell access to users in higher-priced regions. Streaming services, in particular, tend to be vulnerable to this type of abuse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modifying browser settings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some fraudsters manipulate their browser settings to align with the region they want to target. This includes changing the browser’s language, time zone, or geolocation data. For example, a browser set to display Spanish and use the Argentina Time (ART) time zone might trick basic detection systems into thinking the user is in Argentina.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting users’ true location with Fingerprint
&lt;/h2&gt;

&lt;p&gt;Without accurate location detection, fraudsters can slip through the cracks, undermining your carefully designed pricing model. While basic methods like simple IP-based geolocation provide some insight, they can be easily spoofed, leading to inaccurate location data. You need more advanced techniques to see through tools like VPNs, proxies, and spoofed data.&lt;/p&gt;

&lt;p&gt;That’s where Fingerprint can help. Fingerprint’s &lt;a href="https://fingerprint.com/products/smart-signals/" rel="noopener noreferrer"&gt;Smart Signals&lt;/a&gt; give you everything you need to detect user locations and enforce a regional pricing strategy that fraudsters can’t bypass. Fingerprint provides a simple client agent that runs on key pages, like login or checkout, and analyzes over 100 attributes from a user’s browser and device. This data is used to create a &lt;a href="https://fingerprint.com/products/identification/" rel="noopener noreferrer"&gt;unique visitor identifier&lt;/a&gt; to recognize trusted devices, but more importantly, it is also used to detect key signals and attributes to help you gauge user risk and intent.&lt;/p&gt;

&lt;p&gt;For region-based policies, the most relevant signals are &lt;strong&gt;&lt;a href="https://dev.fingerprint.com/docs/smart-signals-reference#ip-geolocation" rel="noopener noreferrer"&gt;IP Geolocation&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://dev.fingerprint.com/docs/smart-signals-reference#vpn-detection-for-browsers" rel="noopener noreferrer"&gt;VPN Detection&lt;/a&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;a href="https://dev.fingerprint.com/docs/smart-signals-reference#geolocation-spoofing-detection" rel="noopener noreferrer"&gt;Geolocation Spoofing Detection&lt;/a&gt;&lt;/strong&gt; for mobile devices.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IP Geolocation&lt;/strong&gt;: Pinpoints the physical location of the true originating IP address. We use multiple techniques to detect the client’s true IP, even when anonymizing tools are in use, ensuring accurate visitor location.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPN Detection&lt;/strong&gt;: Identifies VPN usage by analyzing factors like mismatched time zones, operating system settings, and connections from known VPN providers. This signal returns a simple boolean indicating if a VPN is in use, along with a confidence level for the detection. It also returns which methods were flagged, as well as the originating time zone and country. Check out our article on &lt;a href="https://fingerprint.com/blog/vpn-detection-how-it-works/" rel="noopener noreferrer"&gt;how VPN detection works&lt;/a&gt; for a detailed breakdown of the techniques involved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geolocation Spoofing Detection&lt;/strong&gt;: Specifically for mobile devices, this signal flags when geolocation data appears to have been tampered with, returning a boolean for easy implementation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP Blocklist Matching&lt;/strong&gt;: Available for Enterprise customers, this signal identifies whether a user’s IP address originates from a known Tor exit node or a public proxy provider, giving you additional insights into potential bypassing tactics.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to enforce regional pricing
&lt;/h2&gt;

&lt;p&gt;With Fingerprint Smart Signals, you can pinpoint users’ true locations and catch anyone trying to bypass your regional pricing strategy. Here’s how you can leverage the &lt;strong&gt;IP Geolocation&lt;/strong&gt; and &lt;strong&gt;VPN Detection&lt;/strong&gt; signals to make it happen.&lt;/p&gt;

&lt;p&gt;First, you’ll need a Fingerprint account (&lt;a href="https://dashboard.fingerprint.com/signup" rel="noopener noreferrer"&gt;sign up for a free trial&lt;/a&gt; if you don’t already have one) and your API keys from the &lt;a href="https://dashboard.fingerprint.com/api-keys" rel="noopener noreferrer"&gt;Fingerprint dashboard&lt;/a&gt;. This tutorial will use the Fingerprint CDN and Node SDK, but we have a &lt;a href="https://fingerprint.com/sdk-libraries/" rel="noopener noreferrer"&gt;variety of SDKs&lt;/a&gt; for your favorite framework or language.&lt;/p&gt;

&lt;p&gt;While testing, it’s a good idea to disable any ad blockers because they might interfere with loading Fingerprint scripts from the CDN. For production, you can &lt;a href="https://dev.fingerprint.com/docs/protecting-the-javascript-agent-from-adblockers" rel="noopener noreferrer"&gt;bypass this issue with a proxy integration&lt;/a&gt;, but that’s beyond the scope of this tutorial.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Add the client agent to your checkout
&lt;/h3&gt;

&lt;p&gt;To access the geolocation and VPN signals from Fingerprint, you’ll first need to make an identification request on the pages where accurate location data is required. This example is for a checkout page where a user can activate regional pricing. Load the Fingerprint client agent as early as possible, and then call the &lt;code&gt;get()&lt;/code&gt; method to make an identification request exactly when you need it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Initialize the agent as soon as possible&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fpPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://fpjscdn.net/v3/YOUR_PUBLIC_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FingerprintJS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;FingerprintJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getRegionalPricing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderDetails&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Request identification data when you need it.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fpPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sealedResult&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Include the requestId and/or sealedResult with the order details&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orderPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;orderDetails&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sealedResult&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderPayload&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Send the order payload to your server&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/regional-pricing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// ... additional order logic&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Retrieve the signals server-side
&lt;/h3&gt;

&lt;p&gt;There are several ways to retrieve the generated Smart Signals on your server. After making an identification request on the client side, you can pass the returned &lt;code&gt;requestId&lt;/code&gt; to your server and use the Fingerprint &lt;a href="https://dev.fingerprint.com/reference/pro-server-api" rel="noopener noreferrer"&gt;Server API&lt;/a&gt; to fetch the full identification event results.&lt;/p&gt;

&lt;p&gt;Alternatively, you can set up &lt;a href="https://dev.fingerprint.com/docs/webhooks" rel="noopener noreferrer"&gt;webhooks&lt;/a&gt; to send identification event results directly to an endpoint on your server, linking them using the &lt;code&gt;requestId&lt;/code&gt; from the client side. Another option is to receive the full identification event results directly on the client side in encrypted form as a &lt;code&gt;sealedResult&lt;/code&gt;, then pass them to your server for decryption.&lt;/p&gt;

&lt;p&gt;In this tutorial, we’ll use the encrypted client-side approach, known as &lt;a href="https://dev.fingerprint.com/docs/sealed-client-results" rel="noopener noreferrer"&gt;Sealed Client Results&lt;/a&gt;. We recommend this method because it reduces latency by eliminating the need for an additional request to retrieve the identification event details on your server. Import one of our &lt;a href="https://dev.fingerprint.com/reference/server-sdks" rel="noopener noreferrer"&gt;server SDKs&lt;/a&gt; and use the &lt;code&gt;unsealEventsResponse&lt;/code&gt; method along with the &lt;code&gt;sealedResult&lt;/code&gt; sent from the client side and your &lt;a href="https://dashboard.fingerprint.com/api-keys" rel="noopener noreferrer"&gt;encryption key&lt;/a&gt; to unseal the data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;unsealEventsResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@fingerprintjs/fingerprintjs-pro-server-api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sealedResult&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decryptionKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_ENCRYPTION_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unsealedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;unsealEventsResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sealedResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decryptionKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;algorithm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aes-256-gcm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Extract geolocation and VPN use details
&lt;/h3&gt;

&lt;p&gt;Now that we have the full identification event data decrypted, pull out the specific information we need: the actual geolocation and whether or not VPN has been detected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unsealedData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;ipInfo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;v4&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;geolocation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vpnDetection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unsealedData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;vpn&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see more details on the result data in the &lt;a href="https://dev.fingerprint.com/reference/getevent" rel="noopener noreferrer"&gt;Server API reference&lt;/a&gt; but here is an example of the geolocation object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accuracyRadius"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;km&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;50.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;14.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"postalCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"150 00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Europe/Prague"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Prague"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CZ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Czechia"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"continent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EU"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Europe"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subdivisions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"isoCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hlavni mesto Praha"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the VPN detection output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"originTimezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Europe/Prague"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"originCountry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unknown"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;yet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;supported&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;browsers&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"methods"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timezoneMismatch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"publicVPN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"auxiliaryMobile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Irrelevant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;mobile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;SDKs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;only&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"osMismatch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"relay"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Block VPN and enforce regional pricing based on true location
&lt;/h3&gt;

&lt;p&gt;Next, we’ll use this data to determine the appropriate price based on the user’s actual location and block regional pricing if VPN use is detected. Start by using the true geolocation to determine the correct pricing.&lt;/p&gt;

&lt;p&gt;In this example, pricing is based on the user’s country, and we’ll use a pseudo-function to look up the corresponding price.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;countryPrice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCountryPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’re now using the user’s true location information instead of the location they’re pretending to be in. Before sending the price back to the client, you can also check for VPN usage and block transactions over VPNs for added protection against regional pricing fraud.&lt;/p&gt;

&lt;p&gt;One thing to note is that anonymizing services like Apple’s &lt;a href="https://support.apple.com/en-us/102602" rel="noopener noreferrer"&gt;iCloud Private Relay&lt;/a&gt; can trigger a VPN detection, even if the user &lt;a href="https://discussions.apple.com/thread/254619843" rel="noopener noreferrer"&gt;has not changed their presenting location&lt;/a&gt; using the service. In those cases, you can choose to still return a successful response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;vpnDetection&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;relay&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nx"&gt;vpnDetection&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;timezoneMismatch&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;countryPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In all other cases where VPNs are detected, you can choose to block the regional pricing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vpnDetection&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;It looks like you are using a VPN. Please turn it off and use a regular local internet connection before activating regional pricing.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Stay ahead of fraudsters with smarter data
&lt;/h2&gt;

&lt;p&gt;Regional pricing only works if you can enforce it, and fraudsters using VPNs and proxies are ready to bypass your rules. If you want to keep your pricing strategy intact, you need reliable tools that can cut through their tricks and reveal users’ true locations. With Fingerprint’s Smart Signals you can accurately identify users’ real locations, enforce fair pricing, and block regional pricing fraud with ease.&lt;/p&gt;

&lt;p&gt;Ready to see it in action? Try it out in our &lt;a href="https://demo.fingerprint.com/vpn-detection" rel="noopener noreferrer"&gt;demo&lt;/a&gt; or &lt;a href="https://fingerprint.com/contact-sales/" rel="noopener noreferrer"&gt;contact our team&lt;/a&gt; to learn how Fingerprint can help secure your regional pricing strategy.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>network</category>
    </item>
    <item>
      <title>Tutorial: How to protect your business from referral fraud</title>
      <dc:creator>Keshia Rose</dc:creator>
      <pubDate>Wed, 05 Feb 2025 15:17:00 +0000</pubDate>
      <link>https://dev.to/fingerprintjs/tutorial-how-to-protect-your-business-from-referral-fraud-2kp3</link>
      <guid>https://dev.to/fingerprintjs/tutorial-how-to-protect-your-business-from-referral-fraud-2kp3</guid>
      <description>&lt;p&gt;&lt;em&gt;Written by Martin Capodici&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is referral fraud?
&lt;/h2&gt;

&lt;p&gt;Referral programs are used by hundreds of brands, including T-Mobile, Grubhub, American Express, to drive more sales by rewarding people or businesses that promote their products.&lt;/p&gt;

&lt;p&gt;Referral fraud is the abuse of these programs by fraudsters to make money and obtain unfair discounts. &lt;/p&gt;

&lt;p&gt;Fraudulent referrals are costly, both in terms of money paid to people who don’t deserve it and time spent investigating the fraud. In some cases, credit card fraud accompanies referral fraud, causing additional costs due to refunds and &lt;a href="https://fingerprint.com/blog/how-major-payment-processors-handle-chargebacks/" rel="noopener noreferrer"&gt;chargebacks&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;A &lt;a href="https://www.statista.com/statistics/1297428/leading-fraud-attacks-online-merchants-worldwide/" rel="noopener noreferrer"&gt;2023 study&lt;/a&gt; revealed that 25% of merchants experienced referral fraud, and 34% experienced coupon and discount abuse, which is often related to referral fraud.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do referral programs work?
&lt;/h2&gt;

&lt;p&gt;A referral program rewards promoters according to a result that is beneficial to the business. Here are some examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ride-sharing company Easyride offers customers $20 credit if they get a friend to sign up and spend $20. &lt;/li&gt;
&lt;li&gt;A weightlifting blogger advertises Apex Strength gym on each of their posts. The gym pays the promoter $10 for every person who uses their referral code for a free day pass.&lt;/li&gt;
&lt;li&gt;Level Up Books is a service that sends out curated book recommendations to managers. They sign up with Amazon to receive a percentage of all books sold as a result of links clicked in their emails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are various ways of tracking the performance of referrers, such as the number of clicks, sign-ups, or percentage of sales. This leads to numerous types of referral fraud, each designed to exploit specific weaknesses of the referral program. For example, if you pay a referrer when an account is created with only an email address, they could make a lot of money &lt;a href="https://fingerprint.com/blog/account-creation-fraud/" rel="noopener noreferrer"&gt;creating dozens of fake accounts&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Types of referral fraud
&lt;/h2&gt;

&lt;p&gt;Let’s look at the main categories of referral fraud. While some of these are outright criminal, others, such as breaking the terms of service of a site or exploiting a loophole, are in a gray area.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fake new customers
&lt;/h3&gt;

&lt;p&gt;As I mentioned earlier, when a referral program pays per sign-up, fraudsters can exploit this by creating fake accounts.&lt;/p&gt;

&lt;p&gt;To create enough fake accounts to make this enterprise worthwhile, the fraudsters use bots to create the accounts automatically. These bots can spoof different devices and use different IP addresses using a VPN to evade simple bot or device detection that only relies on IP addresses. Device intelligence, which we will cover later, can help detect these bots.&lt;/p&gt;

&lt;p&gt;If the referral program requires the referred customer to make a successful purchase, the fraudster can use a stolen credit card to make purchases. They will send the item to the cardholder’s billing address if known, or a random address, and profit from the payouts from the referral program. &lt;/p&gt;

&lt;p&gt;When the business finds out it was a fraudulent transaction, they may have already paid the fraudulent referrer, will have incurred a loss due to the cost of the sent goods, and may have to pay chargeback fees to their payment processor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Referral farming
&lt;/h3&gt;

&lt;p&gt;Unique discount codes can be used to track who referred someone to a product. For example, let’s say a YouTube channel called “Shirley’s Health” advertises that a vitamin supplement is 30% off. They tell viewers to use the discount code SHIRLEY. A share of revenue from sales that use the discount code goes to the channel.&lt;/p&gt;

&lt;p&gt;Referral farming is a scheme where the fraudster uploads their unique discount code to promo-code sharing sites. Customers who use these sites typically have decided to buy something, have the order page open, and are now hunting for a discount code. If the fraudster’s discount code is used, they get paid for sales they didn’t help generate — which is a waste of money for the referral program.&lt;/p&gt;

&lt;h3&gt;
  
  
  Return abuse
&lt;/h3&gt;

&lt;p&gt;Return abuse involves referring someone but arranging with them to return the product for a full refund once the referral payout has been made. It takes advantage of both generous return policies and the timing of payouts.&lt;/p&gt;

&lt;p&gt;Return abuse requires coordination with the person returning the goods and cutting them in on the deal — or a fraudster can just set up another fake account to be that “other person,” which requires much less coordination and allows them to keep the full payout amount. Customers who constantly return items get banned by retailers. Therefore, it doesn’t scale as well as other methods of referral fraud.&lt;/p&gt;

&lt;h3&gt;
  
  
  Account cycling
&lt;/h3&gt;

&lt;p&gt;Some referral programs pay based on new account sign-ups. These may pay as soon as an account is verified or only when the new account makes a purchase.&lt;/p&gt;

&lt;p&gt;In either case, account cycling can be used as a referral fraud technique. The idea is to refer a friend to open an account and collect the referral fee. Then, the friend closes the account and uses the referral link or code again to reopen the account with the same details.&lt;/p&gt;

&lt;p&gt;Some referral programs require a transaction after sign-up, which reduces the risk of account cycling but doesn’t eliminate it. It can still be attractive for friends to cycle accounts for each other for products they intend to buy frequently, such as food deliveries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Impact of referral fraud
&lt;/h2&gt;

&lt;p&gt;Referral fraud has financial repercussions for your referral program and should be taken seriously.&lt;/p&gt;

&lt;p&gt;Here are the ways this can happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fraudsters get paid: If the fraudster gets their referral payment before they are caught, your money is lost.&lt;/li&gt;
&lt;li&gt;You get hit with chargebacks: When a stolen credit card is used, you will need to refund the charge, in addition to paying chargeback fees.&lt;/li&gt;
&lt;li&gt;Customers get unintended discounts: Account cycling and referral farming can result in your giving additional discounts that are not budgeted for.&lt;/li&gt;
&lt;li&gt;Your team wastes time: Dealing with refunds, chargebacks, blocking accounts, and investigations wastes your team’s time on fraud mitigation when they could be focusing on something else to help drive more revenue.&lt;/li&gt;
&lt;li&gt;Marketing is negatively impacted: Your marketing campaigns are less effective because money is diverted from genuine referrers to fraudsters. Tracking campaigns also becomes more challenging because fraudulent referrals skew marketing data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Detecting and preventing referral fraud
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Verify referrer details
&lt;/h3&gt;

&lt;p&gt;Asking for more information from referrers will discourage fraudsters because it will force them to reveal their identity or spend money to create fake personas. Asking for email verification, a valid phone number, or government-issued ID documents will reduce referral fraud. However, there is a balance between obtaining just enough verification to prevent fraud and too much verification since you don’t want to deter genuine referrers from signing up and promoting your company.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify referral actions
&lt;/h3&gt;

&lt;p&gt;Consider how you calculate a referrer's payment and the timing of that payment. If it is too easy to get paid — e.g., a sign-up with no sale counts as a referral — then it is easier for the fraudster to meet the payment conditions without generating any real business.&lt;/p&gt;

&lt;p&gt;To verify the referral action, you can require that the referred customer has spent above a certain amount and hasn’t made a return within the return period. If you reward referrers for leads, only pay out the referrer once the lead is marked in your database as genuine; e.g., after you have spoken to them. This requires more time and effort but it is worth it to avoid losing money to fraudsters. &lt;/p&gt;

&lt;h3&gt;
  
  
  Monitor referral activity
&lt;/h3&gt;

&lt;p&gt;Your marketing team is likely tracking referral activity to see which campaigns are successful, and finance may be tracking them for profitability. Using the same data, you can also look for unusual patterns that indicate fraud might be happening. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new or dormant account suddenly generates a high volume of referrals in a short amount of time. &lt;/li&gt;
&lt;li&gt;The number of new accounts created on a particular day is much higher than usual, and you do not know why.&lt;/li&gt;
&lt;li&gt;Customers signing up from countries or states that are out of the ordinary for your business.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Set referral limits
&lt;/h3&gt;

&lt;p&gt;Referral limits let you automate decisions about suspicious activity in a simple way: You can set a monthly limit on how much a referrer can earn. These limits can then be increased for trusted referrers you know, have interviewed, or have generated a lot of legitimate business.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analyze traffic sources
&lt;/h3&gt;

&lt;p&gt;You can keep track of the IP addresses of users creating accounts and check if those IPs are on known blocklists. You can also check if the same IP addresses are involved with multiple accounts since such activity can be considered a red flag and worth investigating further.&lt;/p&gt;

&lt;h3&gt;
  
  
  Block automated abuse
&lt;/h3&gt;

&lt;p&gt;Referral fraudsters use bots to create accounts in bulk automatically. Bots are software that imitates a genuine user and a browser, and takes commands from the fraudster. They are often run from different IP addresses in an attempt to evade detection.&lt;/p&gt;

&lt;p&gt;Bot usage can be detected by analyzing traffic sources, checking for device reuse, and monitoring referral activity. You can also determine how long it takes for someone to sign up or perform specific actions — if they are too fast, they might be a bot. &lt;/p&gt;

&lt;h3&gt;
  
  
  Check for device reuse
&lt;/h3&gt;

&lt;p&gt;The standard techniques to detect device reuse across new accounts are to set a cookie or check IP addresses. Reusing the same device to create multiple accounts is almost certainly a sign of referral fraud or foul play. You can put automatic payment holds on those referrers while they are investigated. &lt;/p&gt;

&lt;p&gt;However, it’s also important to remember that IP addresses are sometimes shared between different people using the same internet service provider (such as students on college campuses). This makes IP reuse a red flag, but this alone is insufficient evidence to ban a customer account. Additionally, cookies-based checks are not foolproof since cookies can be cleared in the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fingerprint device intelligence and Smart Signals
&lt;/h3&gt;

&lt;p&gt;Determined referral fraudsters will use various methods to hide their activity and evade simple detection systems, such as those checking IP addresses. They can launch fraud campaigns using bot networks, VPNs, Tor, compromised cloud accounts, and more.&lt;/p&gt;

&lt;p&gt;To defend against these sophisticated techniques for avoiding detection, &lt;a href="https://fingerprint.com/products/identification/" rel="noopener noreferrer"&gt;Fingerprint device intelligence&lt;/a&gt; creates a unique digital fingerprint, a visitor identifier, for online browsers and devices accessing your website or application. This visitor ID can detect suspicious devices or behavioral patterns in real time. &lt;/p&gt;

&lt;p&gt;Fingerprint analyzes over 100 device attributes like browser configurations, operating system details, and hardware settings to accurately recognize returning visitors. &lt;/p&gt;

&lt;p&gt;Additionally, Fingerprint’s &lt;a href="https://dev.fingerprint.com/docs/smart-signals-overview" rel="noopener noreferrer"&gt;Smart Signals&lt;/a&gt; provides information about your visitors that is useful for detecting suspicious activity, such as whether they are using a VPN or are a bot. &lt;a href="https://fingerprint.com/blog/product-update-suspect-score/" rel="noopener noreferrer"&gt;Fingerprint’s API provides a “Suspect Score” for each device&lt;/a&gt; that you can use to assess the risk of dealing with that visitor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial: Use Fingerprint to prevent fraud against your referral program
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we will use tools from Fingerprint to protect a site against referral fraud. This could be any site with referrals, such as an e-commerce site, a survey site that pays per survey completed, or a business with an offline component completed in person, such as a law firm.&lt;/p&gt;

&lt;p&gt;We will use Node.js for our examples. If you use another technology stack, Fingerprint has &lt;a href="https://fingerprint.com/sdk-libraries/" rel="noopener noreferrer"&gt;libraries&lt;/a&gt; for all the popular frameworks and languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting started
&lt;/h3&gt;

&lt;p&gt;First, &lt;a href="https://dashboard.fingerprint.com/signup" rel="noopener noreferrer"&gt;create a Fingerprint account&lt;/a&gt; and open your &lt;a href="https://dashboard.fingerprint.com/api-keys" rel="noopener noreferrer"&gt;API keys&lt;/a&gt; page. Create a public key now. Keep this page open to copy keys into your code when needed.&lt;/p&gt;

&lt;p&gt;Note: If you’re using an ad blocker, your implementation may not work. Simply turn it off while testing, and learn more about how to protect your production Fingerprint implementation from ad blockers in our &lt;a href="https://dev.fingerprint.com/docs/protecting-the-javascript-agent-from-adblockers" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linking accounts to the device
&lt;/h3&gt;

&lt;p&gt;The Fingerprint visitor ID uniquely identifies a browser or native mobile device. It enables you to detect when a device has been used before on your site, and then link that device to other information you have such as the email address they used, if they have provided one.&lt;/p&gt;

&lt;p&gt;In the first part of the tutorial, we will link new accounts with their visitor ID (which represents the unique device) to spot whether someone is creating many new accounts from the same device. We will then automatically block new accounts made from the same device.&lt;/p&gt;

&lt;p&gt;First, install the Fingerprint API on the sign-up page you must protect, replacing the PUBLIC_API_KEY with the public key from your API keys page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fpPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://fpjscdn.net/v3/PUBLIC_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FingerprintJS&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;FingerprintJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this scenario, we have a sign-up page that requires an email and password. When the user clicks Sign Up, we can resolve the promise shown above and use &lt;code&gt;fp.get()&lt;/code&gt; to instruct Fingerprint to store information about the visitor, their device, and the time of the visit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#signup-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fpPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Send the requestId along with the request you want to protect from suspicious behavior&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/signup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is possible for a fraudster to change or remove the &lt;code&gt;requestId&lt;/code&gt; variable, but that won’t help them bypass the check. Fingerprint can detect this behavior when we verify &lt;code&gt;requestId&lt;/code&gt; on the server.&lt;/p&gt;

&lt;p&gt;We will now add the Node.js server code to verify the request ID and get the associated Fingerprint visitor ID used to identify the device. We can then check if the same device has recently performed a sign-up. Create a private key in &lt;a href="https://dashboard.fingerprint.com/api-keys" rel="noopener noreferrer"&gt;API keys&lt;/a&gt;, replace &lt;code&gt;&amp;lt;&amp;lt;Private Api Key&amp;gt;&amp;gt;&lt;/code&gt; with that key, and change the region to match yours.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;FingerprintJsServerApiClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@fingerprintjs/fingerprintjs-pro-server-api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FingerprintJsServerApiClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;&amp;lt;Private Api Key&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Region&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Global&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/index.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/signup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;visitorId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visitorId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Check how many times this visitor has created an account&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sqlClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;select count(*) as count from user where created &amp;gt; NOW() - INTERVAL '30 DAY' WHERE visitorid = $1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;visitorId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Device has been used to create an account more than twice in the last 30 days, disallow this.&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Link visitorId (i.e. the device) to the user&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sqlClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update user set visitorid = $1 where userid = $2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;visitorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Listening on port 3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Fingerprint bot detection to detect automated tools
&lt;/h3&gt;

&lt;p&gt;We now want to add &lt;a href="https://fingerprint.com/blog/bot-detection/" rel="noopener noreferrer"&gt;bot detection&lt;/a&gt; to protect against the mass creation of accounts. This will protect the site from referral fraud and other unwanted activity that uses bots.&lt;/p&gt;

&lt;p&gt;Fingerprint’s Smart Signals includes a bot detection tool that can tell you if the visitor is likely a bot and, if so, whether it is a “good bot” like a web crawler or a “bad bot” — e.g., someone trying to defraud your site in some way.&lt;/p&gt;

&lt;p&gt;We will check the &lt;a href="https://dev.fingerprint.com/reference/getevent" rel="noopener noreferrer"&gt;event data&lt;/a&gt; object to see if the visitor is likely a bot. In this case, we will reject signups from any kind of bot (good or bad) since we only want human sign-ups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;botDetection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;botd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;botDetection&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;notDetected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Check how many times this visitor has created an account&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;p&gt;Referral fraud wastes time and money while detracting from the marketing goals of the referral program. You should protect against referral fraud using various methods, including monitoring and limits, as well as device intelligence solutions like Fingerprint.&lt;/p&gt;

&lt;p&gt;To try Fingerprint now, check out our &lt;a href="https://dev.fingerprint.com/docs/quick-start-guide" rel="noopener noreferrer"&gt;Getting Started Guide&lt;/a&gt; or &lt;a href="https://fingerprint.com/contact-sales/" rel="noopener noreferrer"&gt;contact our team&lt;/a&gt; to learn more.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Tutorial: Credit card cracking explained — and how to prevent it</title>
      <dc:creator>Keshia Rose</dc:creator>
      <pubDate>Thu, 30 Jan 2025 17:36:41 +0000</pubDate>
      <link>https://dev.to/fingerprintjs/tutorial-credit-card-cracking-explained-and-how-to-prevent-it-10im</link>
      <guid>https://dev.to/fingerprintjs/tutorial-credit-card-cracking-explained-and-how-to-prevent-it-10im</guid>
      <description>&lt;p&gt;&lt;em&gt;Written by Martin Capodici&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you see an unexpected surge of small transactions in your online store, you might be excited — you have new customers! However, watch out, as this could well be a type of credit card fraud called card cracking.&lt;/p&gt;

&lt;p&gt;Card crackers make small payments that test different expiration dates and security (CVV) codes until they find the ones that work. They do this to discover the correct details for a card.&lt;/p&gt;

&lt;p&gt;Credit card cracking, also called card testing, creates additional costs for the merchant due to a &lt;a href="https://fingerprint.com/blog/build-better-chargeback-cases-with-device-identification/" rel="noopener noreferrer"&gt;chargeback fee&lt;/a&gt; for each disputed transaction. If this happens too often, the payment processor may increase their general fees. This is a growing problem: about $18 billion was lost worldwide to credit card fraud in 2014, rising to &lt;a href="https://www.statista.com/statistics/1394119/global-card-fraud-losses/" rel="noopener noreferrer"&gt;over $32 billion in 2022&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How can you prevent this from happening on your site? In this tutorial, we will talk through the main layers of defenses you can build against this threat. We will then go through code examples using Fingerprint to better protect an e-commerce site.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is card cracking?
&lt;/h2&gt;

&lt;p&gt;Card cracking is part of a process in which fraudsters attempt to obtain working credit cards that they can use to make illegal purchases. &lt;/p&gt;

&lt;p&gt;First, they purchase a list of credit card numbers from an illicit source, often on the dark web. These numbers may have been skimmed from physical card readers, exposed in a data breach, or entered online into scam forms. However, these lists may not have the necessary information to successfully make a purchase since you usually need the expiration date, the security code, and sometimes other information, too.&lt;/p&gt;

&lt;p&gt;The credit card cracker attempts to make small payments by guessing the missing information. Because the fraudster has a lot of card numbers and a few tries for each card, they eventually will succeed in finding the right combination. Once they have a working card with all the information, they can then go on to make bigger transactions.&lt;/p&gt;

&lt;p&gt;Card testing is a similar practice to card cracking. With card testing, the fraudster has the full card information to begin with, and they perform a test payment to verify that the card is still valid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Impact of credit card cracking
&lt;/h2&gt;

&lt;p&gt;Card cracking affects merchants in many ways, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Financial losses:&lt;/strong&gt; Merchants must refund payments from unauthorized transactions made with cracked credit cards and also pay chargeback fees. If the merchant has shipped or delivered anything, that is an additional cost. On top of that, if said merchant has too many chargebacks, the payment provider may punish the merchant with extra fees or less favorable terms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational costs:&lt;/strong&gt; Sellers must investigate and monitor fraudulent transactions, including credit card cracking, to avoid shipping products or providing services when they occur. They also need to decide how to manage or reduce the occurrence of these events. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reputational damage:&lt;/strong&gt; For reputable brands, their name appearing on card statements as fraud could result in negative reviews and word of mouth. Additionally, if word gets out that a merchant has allowed card-cracking transactions, this could cause more attempts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How do card cracking attacks work?
&lt;/h2&gt;

&lt;p&gt;Card cracking needs to be fast. The idea is to test a large number of cards and find the needle in a haystack; that is, a card that works and doesn’t get blocked when attempting to crack it. In addition, as a fraudster, you want to avoid detection. Fraudsters using the same browser and computer with the same IP address run a higher risk of being spotted by even the most basic detection systems.&lt;/p&gt;

&lt;p&gt;To test a large number of cards quickly and evade detection, here are some tools crackers use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automated bots:&lt;/strong&gt; These will run through lists of credit cards and possible details, testing them out on different sites. &lt;a href="https://fingerprint.com/blog/bot-detection/" rel="noopener noreferrer"&gt;Bots run in parallel&lt;/a&gt;, taking instructions from a central command for rapid testing and elimination.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Device/browser spoofing:&lt;/strong&gt; When using bots, the card cracker will want to make it look like a human is interacting with the site. One way to appear less bot-like is to spoof (pretend to be) the browser of a legitimate customer. This can be done by automating a real web browser so that the actions, typing, and button clicking are happening on an actual screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple IPs/VPNs:&lt;/strong&gt; Sites can block IP addresses, which are network identifiers that provide some idea of who is visiting the site. Card crackers will use a virtual private network (VPN), which lets them switch to different IP addresses as needed to avoid being blocked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Botnets:&lt;/strong&gt; Computers infected with viruses can be remote-controlled, and a fleet of infected computers is called a botnet. For the cracker, these bots have the advantage of having home internet IP addresses, which are much less likely than VPNs to be blocked.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ways to detect and prevent card cracking scams
&lt;/h2&gt;

&lt;p&gt;Credit card cracking is a fairly sophisticated technique. Luckily, there are many ways to detect card cracking and fraud in general, including:&lt;/p&gt;

&lt;h3&gt;
  
  
  Payment processor tools
&lt;/h3&gt;

&lt;p&gt;Many merchants use a payment processor, like Stripe, instead of working directly with Mastercard or Visa. These payment processors often provide tools to help detect credit card cracking and other fraud. One example is Stripe Radar, which uses machine learning to detect fraud and is built into Stripe’s payment services. &lt;/p&gt;

&lt;p&gt;Payment processor tools like Stripe Radar only use financial data to detect fraud and do not look at information about the visitor and their device. Therefore, we recommend pairing them with other strategies in this list, such as device intelligence, to block fraud attempts that the payment processor’s tools may miss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Card authorization techniques
&lt;/h3&gt;

&lt;p&gt;3DS, or 3D Secure, is a technique that requires additional security verification from a customer. It is a technical standard from Visa and Mastercard that redirects customers to their credit card issuer for authentication. The customer will be asked to provide a password or a one-time code sent to their phone. This is an excellent method to foil card crackers, but unfortunately, it adds additional friction for the user and relies on the issuing bank to implement 3DS, and not all have done so.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-authorization holds
&lt;/h3&gt;

&lt;p&gt;A pre-authorization hold refers to the process where funds are secured from the cardholder but not immediately processed. This is often used in hospitality; for example, when booking a hotel room or a table at a popular restaurant. It can also be used for other kinds of e-commerce purchases (although it is rarer). &lt;/p&gt;

&lt;p&gt;Pre-authorization holds may deter credit card cracking because the cracker needs to wait for the final transaction to complete. However, using a hold is a significant change to the customer experience, so it needs to make sense for the business.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transaction velocity monitoring
&lt;/h3&gt;

&lt;p&gt;Profitable cracking requires attempting thousands of test payments reasonably quickly. If you’re monitoring usage patterns, this may look like an unusual spike in activity. For example, if a fraudster uses a single account to do this, that merchant can keep track of how many different cards have been successfully or unsuccessfully used in the last 24 hours, 7 days, or longer. The merchant can put that account on hold for manual review if the count is suspiciously high.&lt;/p&gt;

&lt;h3&gt;
  
  
  Device intelligence and fingerprinting
&lt;/h3&gt;

&lt;p&gt;Device fingerprinting provides a unique identifier for a single device that is hard to change. This &lt;a href="https://fingerprint.com/blog/device-intelligence-explainer/" rel="noopener noreferrer"&gt;device intelligence can detect when a fraudster&lt;/a&gt; is using the same device to impersonate multiple people. This is possible because information about the device and its connection is revealed when you visit any website. In addition, device intelligence systems can detect potentially suspicious activity, such as when a virtual machine is being used instead of an actual device.&lt;/p&gt;

&lt;p&gt;Fingerprint provides both these capabilities and more via its Identification and &lt;a href="https://fingerprint.com/products/smart-signals/" rel="noopener noreferrer"&gt;Smart Signals solutions&lt;/a&gt;. Fingerprint accurately recognizes returning visitors so you can detect repeated use of devices, associate a device with a user or payment, and detect suspicious activity based on a multitude of threat signals.&lt;/p&gt;

&lt;p&gt;Fingerprint can also provide the “device ID” / “fingerprint” inputs necessary for &lt;a href="https://developer.mastercard.com/product/first-party-trust/" rel="noopener noreferrer"&gt;Mastercard’s First-Party Trust Program&lt;/a&gt; and &lt;a href="https://usa.visa.com/content/dam/VCOM/regional/na/us/support-legal/documents/compelling-evidence-3.0-merchant-readiness-mar2023.pdf" rel="noopener noreferrer"&gt;Visa’s Compelling Evidence 3.0&lt;/a&gt;, which are programs that give the merchant a chance to dispute an unfair chargeback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial: Use Fingerprint to prevent credit card cracking on your payment portal
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we are going to use Fingerprint to protect an e-commerce site using techniques such as bot detection, rate limits, blocked visitors, and more.&lt;/p&gt;

&lt;p&gt;We will assume you are running an e-commerce site and accepting card payments on your site. You may be using Stripe to do this or are directly processing credit card transactions on your systems. &lt;/p&gt;

&lt;p&gt;We will use NodeJS for our examples. (Fingerprint has &lt;a href="https://fingerprint.com/sdk-libraries/" rel="noopener noreferrer"&gt;libraries&lt;/a&gt; for all the popular frameworks and languages, and the implementation will be similar in those, too.)&lt;/p&gt;

&lt;p&gt;To get started quickly, let’s use some example code from Stripe that simulates some of the code an e-commerce store might have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/stripe-samples/accept-a-payment/
&lt;span class="nb"&gt;cd &lt;/span&gt;custom-payment-flow/server/node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a file &lt;code&gt;.env&lt;/code&gt; in the root of the &lt;code&gt;custom-payment-flow/server/node&lt;/code&gt; folder and add these settings. &lt;strong&gt;Optionally,&lt;/strong&gt; update the appropriate test account values if you have a Stripe account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Stripe API keys - see https://stripe.com/docs/development/quickstart#api-keys
STRIPE_PUBLISHABLE_KEY=pk_test...
STRIPE_SECRET_KEY=sk_test...

# Required to verify signatures in the webhook handler.
# See README on how to use the Stripe CLI to test webhooks
STRIPE_WEBHOOK_SECRET=whsec_...

# Path to front-end implementation. Note: PHP has it's own front end implementation.
STATIC_DIR=../../client/html
DOMAIN=http://localhost:4242
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, run these commands to get the server up and running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now browse to &lt;a href="http://localhost:4242" rel="noopener noreferrer"&gt;http://localhost:4242&lt;/a&gt; to see the example site, which looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="/img/uploads/example-site-credit-card-cracking-tutorial.png" class="article-body-image-wrapper"&gt;&lt;img src="/img/uploads/example-site-credit-card-cracking-tutorial.png" alt="Image of example site for credit card cracking prevention tutorial"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking the &lt;strong&gt;Card&lt;/strong&gt; link takes you to a self-hosted payment page, that, at the moment, doesn't do any detection of suspicious activity. Let’s use Fingerprint to try to detect credit card cracking as well as fraud in general.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Fingerprint bot detection to detect automated tools
&lt;/h3&gt;

&lt;p&gt;The first layer of protection we will add is &lt;a href="https://fingerprint.com/products/bot-detection" rel="noopener noreferrer"&gt;Bot Detection&lt;/a&gt;. Since bots are used by credit card crackers and not by genuine users, this is a good check to deter credit card crackers. In this example, we will block payments when we detect the user is a bot. &lt;/p&gt;

&lt;p&gt;Install the Fingerprint library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @fingerprintjs/fingerprintjs-pro-server-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://dashboard.fingerprint.com/signup" rel="noopener noreferrer"&gt;Sign up for a free trial&lt;/a&gt;, and you can then &lt;a href="https://dashboard.fingerprint.com/api-keys" rel="noopener noreferrer"&gt;create a secret API key&lt;/a&gt;. Armed with this API key, you can now add the following code to the top of &lt;code&gt;server.js&lt;/code&gt;, setting the &lt;code&gt;apiKey&lt;/code&gt; value with your key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;FingerprintJsServerApiClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fingerprintjs/fingerprintjs-pro-server-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FingerprintJsServerApiClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;&amp;lt;Private Api Key&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Region&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Global&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bot detection code will be added to the route &lt;code&gt;create-payment-intent&lt;/code&gt;. This code is called when the user clicks &lt;strong&gt;Pay&lt;/strong&gt;. We want to run bot detection when this route is called. To do this, we will add code to the front end for Fingerprint to analyze the browser and device, and code in the backend to get a verdict from the Fingerprint Identification service.&lt;/p&gt;

&lt;p&gt;In the server, we need to check for the &lt;code&gt;requestId&lt;/code&gt; passed from the client, validate this, and determine if Fingerprint thinks this request is a bot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/create-payment-intent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// New code, that takes the requestId from the client, and verifies it with the&lt;/span&gt;
  &lt;span class="c1"&gt;// Fingerprint service, using the bot-detection results:&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;paymentMethodType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;paymentMethodOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isBot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;botd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;notDetected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isBot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Payment failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;


  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Existing code&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are some changes to the client; first, we need to load the Fingerprint API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Load the library immediately, so it is ready by the time the user clicks Pay&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fpPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://fpjscdn.net/v3/&amp;lt;&amp;lt;Public Api Key&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FingerprintJS&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;FingerprintJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c1"&gt;// Existing code&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: You need to replace &lt;code&gt;&amp;lt;&amp;lt;Public Api Key&amp;gt;&amp;gt;&lt;/code&gt; with your &lt;a href="https://dashboard.fingerprint.com/api-keys" rel="noopener noreferrer"&gt;public API key&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When the user clicks &lt;strong&gt;Pay&lt;/strong&gt;, we then send an identification request to Fingerprint using &lt;code&gt;fp.get()&lt;/code&gt;. This is for two reasons: First, we need to indicate that the user has taken action and not just visited the page. Second, we need to get a &lt;code&gt;requestId&lt;/code&gt; to be used for server validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Using the Fingerprint library, get a requestId for this session&lt;/span&gt;

&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fpPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Existing code&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, pass the &lt;code&gt;requestId&lt;/code&gt; to the server when making the request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Include the requestId with data sent up to the server&lt;/span&gt;

        &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;usd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;paymentMethodType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- Add this line&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;requestId&lt;/code&gt; is being sent to your server where you can use your secret API key to get the full identification event. This information includes the &lt;code&gt;visitorId&lt;/code&gt; as well as the detection result flagging any bot activity. While the &lt;code&gt;visitorId&lt;/code&gt; is returned on the client from &lt;code&gt;fp.get()&lt;/code&gt;, you should avoid using it directly and use the &lt;code&gt;requestId&lt;/code&gt; to get the full event directly from Fingerprint to thwart any client-side tampering. &lt;/p&gt;

&lt;p&gt;You can read more about this here: &lt;a href="https://dev.fingerprint.com/docs/protecting-from-client-side-tampering" rel="noopener noreferrer"&gt;Protecting from client-side tampering&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Proxying requests to avoid ad blockers
&lt;/h4&gt;

&lt;p&gt;You should also strongly consider proxying requests to Fingerprint through your own domain. This prevents ad blockers from interfering with the operation of the Fingerprint front-end code. We have detailed information on how to do this here: &lt;a href="https://dev.fingerprint.com/docs/protecting-the-javascript-agent-from-adblockers" rel="noopener noreferrer"&gt;Evading ad blockers with proxy integrations&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Fingerprint visitor IDs to enforce rate limits
&lt;/h2&gt;

&lt;p&gt;We can check how many times a visitor has tried to make a transaction (successful or otherwise) in the last 24 hours. If this is above a reasonable threshold, say 5 transactions, then we can block the request or require some other form of verification like a one-time passcode sent via SMS.&lt;/p&gt;

&lt;p&gt;Depending on your use case you can tune the limit to fit your needs or enforce a limit only for non-verified users of your site.&lt;/p&gt;

&lt;p&gt;To enforce a rate limit, we can record the visitor ID and timestamp for each transaction attempt. Then when the user clicks the &lt;strong&gt;Pay&lt;/strong&gt; button, we check how many transactions have been attempted in the last 24 hours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Inside /create-payment-intent handler&lt;/span&gt;
&lt;span class="c1"&gt;// Set up DB connection (using Postgres for this example)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pgp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg-promise&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pgp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgresql://username:password@localhost:5432/database&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;visitorId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visitorId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Block requests when more than 5 attempts were made over 24 hours:&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT COUNT(*) FROM visitor_payment_attempt WHERE visitor_id = $1 AND created_at &amp;gt; NOW() - INTERVAL &lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;24 HOURS&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;visitorId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Payment failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Don't leak specific information about how they were detected&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paymentIntent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentIntents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paymentIntentId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;paymentIntent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Store the attempt:&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;none&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INSERT INTO visitor_payment_attempt (visitor_id, created_at) VALUES($1, $2)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;visitorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The credit card cracker’s job is now much harder. They will be blocked after 5 tries in a day and will not be able to test any more cards on your site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Block known fraudsters with their visitor ID
&lt;/h2&gt;

&lt;p&gt;As seen above, when a visitor clicks the &lt;strong&gt;Pay&lt;/strong&gt; button, we get a payment intent identifier from Stripe. If the visitor then makes a transaction that later becomes a chargeback, Stripe can provide that same payment intent identifier for that chargeback. This alone is somewhat useful, but not enough to identify the fraudster if they try this again.&lt;/p&gt;

&lt;p&gt;To solve this using Fingerprint, we can record the &lt;code&gt;visitorId&lt;/code&gt; along with the payment intent identifier when a visitor clicks &lt;strong&gt;Pay&lt;/strong&gt;. Later on, when you have a chargeback, you can find out which device and browser was involved and block them, using these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make a call to Stripe to get the payment intent identifier of a chargeback&lt;/li&gt;
&lt;li&gt;Look up the &lt;code&gt;visitorId&lt;/code&gt; involved with that payment intent in your database.&lt;/li&gt;
&lt;li&gt;Block that &lt;code&gt;visitorId&lt;/code&gt; from further transactions on your site.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code changes we need in our server to store the link between &lt;code&gt;visitorId&lt;/code&gt; and payment intent, and check for blocklisted visitors, are as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Inside /create-payment-intent handler&lt;/span&gt;

&lt;span class="c1"&gt;// ... acquire DB connection ...&lt;/span&gt;


&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;visitorId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visitorId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ... other checks ...&lt;/span&gt;

&lt;span class="c1"&gt;// Look up the visitorId from the database against the blacklist, and&lt;/span&gt;
&lt;span class="c1"&gt;// if they are on the list, block them:&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT * FROM blacklist WHERE visitor_id = $1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;visitorId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Payment failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Don't leak specific information about how they were detected&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paymentIntent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentIntents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paymentIntentId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;paymentIntent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ... other insert statements ...&lt;/span&gt;

&lt;span class="c1"&gt;// Store the paymentIntentId and visitorId for future use and blacklisting:&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;none&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INSERT INTO visitor_payment_intents(visitor_id, payment_intent_id) VALUES($1, $2)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;visitorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;paymentIntentId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to recording the payment intent and visitor ID, we can also store their user ID (if logged in), and the email address or physical address they used when making the purchase. We can store information from the &lt;a href="https://dev.fingerprint.com/reference/getevent" rel="noopener noreferrer"&gt;Fingerprint API&lt;/a&gt; such as IP addresses and geolocation information too.&lt;/p&gt;

&lt;p&gt;This data can aid investigations into fraud that may be carried out months after it has occurred.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways to prevent credit card cracking
&lt;/h2&gt;

&lt;p&gt;Credit card cracking is an insidious practice that causes a lot of cost and hassle for merchants and credit card companies. &lt;/p&gt;

&lt;p&gt;The best defense is layered, taking advantage of the tools provided by payment providers like Stripe, card authorization, multiple-factor authentication, and device intelligence tools like Fingerprint.&lt;/p&gt;

&lt;p&gt;To try Fingerprint now, check out our &lt;a href="https://dev.fingerprint.com/docs/quick-start-guide" rel="noopener noreferrer"&gt;Getting Started Guide&lt;/a&gt;, or &lt;a href="https://fingerprint.com/contact-sales/" rel="noopener noreferrer"&gt;contact our team&lt;/a&gt; to find out more.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
