<?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: FindIP</title>
    <description>The latest articles on DEV Community by FindIP (@findip).</description>
    <link>https://dev.to/findip</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4179492%2F9f6e6641-0bba-4bbf-8680-2c67339fa86b.png</url>
      <title>DEV Community: FindIP</title>
      <link>https://dev.to/findip</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/findip"/>
    <language>en</language>
    <item>
      <title>We made visitor risk scoring free. Here's how to add it to signup and checkout</title>
      <dc:creator>FindIP</dc:creator>
      <pubDate>Mon, 12 Oct 2026 14:22:14 +0000</pubDate>
      <link>https://dev.to/findip/we-made-visitor-risk-scoring-free-heres-how-to-add-it-to-signup-and-checkout-2216</link>
      <guid>https://dev.to/findip/we-made-visitor-risk-scoring-free-heres-how-to-add-it-to-signup-and-checkout-2216</guid>
      <description>&lt;p&gt;Fake signups and trial abuse cost money before anyone pays you: compute, emails, support time. The usual fixes are a CAPTCHA on every visitor, which costs you real signups, or a fraud vendor that bills per lookup.&lt;/p&gt;

&lt;p&gt;We are the FindIP team, a small group of developers. For years, developers have used our IP intelligence API to find out where a connection really comes from: its location, and whether it is a VPN, a proxy, Tor or a datacenter. In all that time we watched them build the same thing on top of it, again and again: look up the IP on signup, keep some state, decide whether to add friction. So we built it properly, once, and made it free. It is called &lt;strong&gt;FindIP Shield&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.findip.net/shield/demo?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=shield_oct_2026&amp;amp;utm_content=dev_article_intro_demo" rel="noopener noreferrer"&gt;See your own connection scored in the live demo&lt;/a&gt;&lt;/strong&gt;. No signup needed.&lt;/p&gt;

&lt;p&gt;What you get, free:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A 0 to 100 risk score with reasons&lt;/strong&gt; on every signup, login, lead and checkout attempt: VPN, proxy, Tor, hosting network, known-malicious address.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotation detection.&lt;/strong&gt; A session whose IP, network or country changes midway is flagged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A response per form that you choose:&lt;/strong&gt; watch only, slow down, a Turnstile check, or stop. Installing changes nothing until you pick one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No metering.&lt;/strong&gt; API calls are unlimited under fair usage, and each site records up to 10,000 events a day. No credit card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy by design.&lt;/strong&gt; The SDK is MIT licensed and never reads what a visitor types.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post shows how to add it in four steps. The pattern works without Shield too, so if you take one thing away, take the rule below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; a browser-side risk result is for visibility and user experience. If a result is going to deny access, stop a payment or add real friction, verify the session on your server first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The case we are designing for
&lt;/h2&gt;

&lt;p&gt;A product offers a free trial and a discounted first payment. Someone creates several accounts with different emails. The per-IP limit catches the first burst. Later attempts arrive through different proxies and hosting providers, one or two per address, and look like ordinary signups.&lt;/p&gt;

&lt;p&gt;No single detail proves anything. The useful part is the combination: repeated signup events, changing infrastructure, and elevated network risk around the same valuable action. The application needs three answers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What action just happened?&lt;/li&gt;
&lt;li&gt;What explains the risk result?&lt;/li&gt;
&lt;li&gt;What is the least disruptive response that still protects the product?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 1: report the action, not the person
&lt;/h2&gt;

&lt;p&gt;Install the SDK once near the browser entry point. The script-tag form is the same idea for sites without a bundler.&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; @findip/shield
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getSession&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@findip/shield&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;siteKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pub_xxxxxxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;privacyMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;balanced&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;autoTrack&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;autoDetectForms&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK recognises common signup, login, lead and checkout forms from their attributes, and reports the submit itself as &lt;code&gt;signup_attempt&lt;/code&gt;, &lt;code&gt;login_attempt&lt;/code&gt; and so on. Do not send the same attempt again from your own code, or it is counted twice. Use &lt;code&gt;track()&lt;/code&gt; for what the form cannot know: the outcome, or an attempt that fires no form &lt;code&gt;submit&lt;/code&gt; event, such as a request sent from a click handler.&lt;/p&gt;

&lt;p&gt;Keep the context small and business-shaped. A plan name or an email domain explains an event; a raw email, a password or a card number has no place in the payload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Form detection already sent signup_attempt. Report the outcome.&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="nf"&gt;track&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_success&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;email_domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;free&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="k"&gt;if &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="nx"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// No result came back. This is "we do not know", not "safe".&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checkout follows the same rule: &lt;code&gt;transaction_amount&lt;/code&gt; and &lt;code&gt;currency&lt;/code&gt; are context; payment details are not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: decide what each result means before launch
&lt;/h2&gt;

&lt;p&gt;The result carries a score when intelligence is available, a recommendation and readable reasons (proxy, Tor, hosting network, a rotating IP inside the session, a malicious-IP category). Decide the response table now, not after the first suspicious checkout:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;th&gt;Reasonable response&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Continue. Rate limits and account checks still apply.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;monitor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Continue, keep the event for correlation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;challenge&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Something proportionate: email confirmation, MFA, a Turnstile check.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;block&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Only under a documented server-side policy, with a recovery path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No result, or &lt;code&gt;unknown&lt;/code&gt; status from the server check&lt;/td&gt;
&lt;td&gt;Intelligence was unavailable. Not a score of zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The mistake that makes risk scoring hateful is turning one signal into a hard rule. A VPN flag is not proof of fraud. Corporate networks, privacy-conscious customers, mobile carriers and travellers all show up with signals that look odd in isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: do not make the browser a trust boundary
&lt;/h2&gt;

&lt;p&gt;Anyone who can open developer tools can rewrite browser code or fabricate a result. So the browser never decides. It passes the session ID along with the business request, and the server verifies it with a secret key that only the server holds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Browser: attach the session to the signup request.&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;sessionId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&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="s1"&gt;/api/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="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;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="na"&gt;findip_session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sessionId&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server: verify before enforcing anything.&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="s1"&gt;https://shield.findip.net/v1/shield/sessions/verify&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="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FINDIP_SHIELD_SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;session_id&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;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findip_session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;event&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_attempt&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verified&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;response&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verified&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;risk_status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unknown&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;continueWithoutRiskBasedEnforcement&lt;/span&gt;&lt;span class="p"&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;verified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommendation&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;rejectUnderDocumentedPolicy&lt;/span&gt;&lt;span class="p"&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;verified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommendation&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;challenge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;challenge_passed&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="nf"&gt;requireAdditionalVerification&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;The verified response also describes the whole session: distinct IPs and networks seen, whether the IP or ASN rotated, and network flags that were true for any event. That is the record you want in a fraud queue or an order note, and it comes from the server, not from the page.&lt;/p&gt;

&lt;p&gt;For plain HTML forms, &lt;code&gt;sessionField: true&lt;/code&gt; makes the SDK add the session ID as a hidden field, so the server does not depend on a cookie that a privacy-minded browser may refuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: choose a privacy mode on purpose
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Fits&lt;/th&gt;
&lt;th&gt;Behaviour&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;strict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Consent denied, or minimal collection&lt;/td&gt;
&lt;td&gt;Session-only identity, no visitor cookie, user agent, language and time zone only. Form metadata, never values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;balanced&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A sensible default&lt;/td&gt;
&lt;td&gt;Adds a 30-day first-party visitor ID, screen and device details, and an optional picture of a form with every value blanked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;advanced&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Longer retention&lt;/td&gt;
&lt;td&gt;The same fields as &lt;code&gt;balanced&lt;/code&gt;, marked for extended retention&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every mode excludes what a visitor types: passwords, card numbers, emails, phone numbers and every other field value. No keystrokes, no page HTML, no screenshot of the page and no session recording. The one picture is the blanked form in &lt;code&gt;balanced&lt;/code&gt; and &lt;code&gt;advanced&lt;/code&gt;, and &lt;code&gt;captureFormSnapshots: false&lt;/code&gt; turns it off. On ingestion the event is rebuilt from an allowlist rather than stored as whatever the browser sent.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we would not do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Switch on automatic blocking on day one. Watch ordinary traffic first, long enough to learn what your employees, your corporate customers and your mobile users look like.&lt;/li&gt;
&lt;li&gt;Treat an unavailable lookup as a score of zero. Handle &lt;code&gt;unknown&lt;/code&gt; explicitly on both sides.&lt;/li&gt;
&lt;li&gt;Reject a payment on a browser-side score. Verify, then decide, and give a legitimate user a way to recover when a challenge fails.&lt;/li&gt;
&lt;li&gt;Collect "just the email" for convenience. Hash it in the browser if you need to tie a session to an account; the SDK does that for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Before adding friction
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Confirm the allowed domain is right and the first event reaches the dashboard.&lt;/li&gt;
&lt;li&gt;Run in visibility mode long enough to understand normal traffic.&lt;/li&gt;
&lt;li&gt;Use the lowest privacy mode that gives the context you actually need.&lt;/li&gt;
&lt;li&gt;Handle &lt;code&gt;unknown&lt;/code&gt; explicitly in browser and server code.&lt;/li&gt;
&lt;li&gt;Verify any decision that stops a signup, payment or account action.&lt;/li&gt;
&lt;li&gt;Give legitimate users a recovery path when a challenge fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;See it first:&lt;/strong&gt; the &lt;a href="https://www.findip.net/shield/demo?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=shield_oct_2026&amp;amp;utm_content=dev_article_demo" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; scores your own connection and shows a sample dashboard. No signup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install it:&lt;/strong&gt; the &lt;a href="https://www.findip.net/docs/shield/quickstart?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=shield_oct_2026&amp;amp;utm_content=dev_article_quickstart" rel="noopener noreferrer"&gt;quickstart&lt;/a&gt; covers npm, a script tag, WordPress, Shopify, Google Tag Manager and a no-code Cloudflare Worker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the code:&lt;/strong&gt; the SDK is on &lt;a href="https://github.com/findip-net/findip-shield-js" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; under the MIT licence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We would like to hear what would make this more useful to you. Questions about the trust model are welcome in the comments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we are the FindIP team, and Shield is our product. It is free in its current preview. The full guide is the canonical version at &lt;a href="https://www.findip.net/Docs/visitor-risk-scoring-signup-checkout?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=shield_oct_2026&amp;amp;utm_content=dev_article_canonical" rel="noopener noreferrer"&gt;findip.net&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>security</category>
      <category>webdev</category>
      <category>saas</category>
    </item>
  </channel>
</rss>
