Overview
This tutorial walks through implementing Fingerprint to prevent SMS pumping attacks, where fraudsters use bots to repeatedly trigger one-time passcode (OTP) messages or verification texts to many phone numbers in order to test stolen numbers or abuse your SMS gateway for profit. You’ll begin with a starter app that includes a mock account creation page and a basic flow to send an SMS verification code. From there, you’ll add the Fingerprint JavaScript agent to identify each visitor and use server-side logic with Fingerprint data to detect and block automated SMS verification requests. By the end, you’ll have a sample app that rejects SMS-pumping bots and can be customized to fit your use case and business rules. This tutorial uses just plain JavaScript and a Node server with SQLite on the back end. For language- or framework-specific setups, see our quickstarts.Estimated time: < 15 minutes
This tutorial requires the Bot Detection Smart Signal, which is only available
on paid plans.
Prerequisites
Before you begin, make sure you have the following:- A copy of the starter repository (clone with Git or download as a ZIP)
- Node.js (v20 or later) and npm installed
- Your favorite code editor
- Basic knowledge of JavaScript
1. Create a Fingerprint account and get your API keys
- Sign up for a free Fingerprint trial, or log in if you already have an account.
- After signing in, go to the API keys page in the dashboard.
- Save your public API key, which you’ll use to initialize the Fingerprint JavaScript agent.
- Create and securely store a secret API key for your server. Never expose it on the client side. You’ll use this key on the backend to retrieve full visitor information through the Fingerprint Server API.
2. Set up your project
- Clone or download the starter repository and open it in your editor:
Terminal
- This tutorial will be using the
sms-pumpingfolder. The project is organized as follows:
Project structure
- Install dependencies:
Terminal
- Copy or rename
.env.exampleto.env, then add your Fingerprint API keys:
Terminal
- Start the server:
Terminal
- Visit http://localhost:3000 to view the mock account creation page from the starter app. You can test the basic flow by entering an email and phone number, then clicking Send code via SMS.
- Then try triggering SMS requests using the included headless bot script
test-bot.js. While the app is running, executenode test-bot.jsand observe that the automated script successfully requests SMS codes. By default, the server does not distinguish between bots and real users:
Terminal
3. Add Fingerprint to the front end
In this step, you’ll load the Fingerprint client when the page loads and trigger identification when the user clicks Send code via SMS. The client returns both avisitorId and a requestId. Instead of relying on the visitorId from the browser, you’ll send the requestId to your server along with the account creation payload. The server will then call the Fingerprint Events API to securely retrieve the full identification details, including bot detection and other signals.
- At the top of
public/index.js, load the Fingerprint JavaScript agent:
public/index.js
- Make sure to change
regionto match your workspace region (e.g.,eufor Europe,apfor Asia,usfor Global (default)). - Near the bottom of
public/index.js, the Send code via SMS button already has an event handler for submitting the account details. Inside this handler, request visitor identification from Fingerprint using theget()method and include the returnedrequestIdwhen sending the request to the server:
public/index.js
get() method sends signals collected from the browser to Fingerprint servers, where they are analyzed to identify the visitor. The returned requestId acts as a reference to this specific identification event, which your server can later use to fetch the full visitor details.
For lower latency in production, check out our documentation on using Sealed Client Results to return full identification details as an encrypted payload from the get() method.
4. Receive and use the request ID to get visitor insights
Next, pass therequestId through to your server-side account creation logic, initialize the Fingerprint Server API client, and fetch the full visitor identification event so you can access the trusted visitorId and Bot Detection Smart Signal.
- In the back end, the
server/server.jsfile defines the API routes for the app. Update the/api/send-smsroute there to also extractrequestIdfrom the request body and pass it into thesendSMSfunction:
server/server.js
- The
server/sms.jsfile contains the logic for handling SMS verification requests. Start by importing and initializing the Fingerprint Server API client there, and load your environment variables withdotenv:
server/sms.js
- Make sure to change
regionto match your workspace region (e.g.,EUfor Europe,APfor Asia,Globalfor Global (default)). - Update the
sendSMSfunction to also extractrequestIdand use it to fetch the full identification event details from Fingerprint:
server/sms.js
requestId, the Fingerprint server client will retrieve the full data for the visitor identification request. The returned object will contain the visitor ID, IP address, device, and browser details, and Smart Signals like bot detection, browser tampering detection, VPN detection, and more.
You can see a full example of the event structure and test it with your own device in our demo playground.
For additional checks to ensure the validity of the data coming from your front end, view how to protect from client-side tampering and replay attacks in our documentation.
5. Block SMS pumping bots
SMS pumping attacks rely heavily on automated account creations, so rejecting bots outright can stop the abuse. Fingerprint returnsnotDetected if no bot activity is found, good for known bots, like search engines, and bad for other automation tools. Any visitor identification that does not return notDetected can be blocked from sending SMS codes.
- Continuing in the
sendSMSfunction inserver/sms.js, check the bot signal returned in theeventobject and block bots:
server/sms.js
- Below the bot detection check, add a condition that reads the Suspect Score from the
eventobject and blocks the request if it exceeds a chosen threshold (for example, 20):
server/sms.js
6. Recognize repeat offenders by visitor ID
As a secondary measure, you can log thevisitorId along with each SMS verification request to spot suspicious activity. This lets you recognize and block the same device even if they clear cookies, change IPs, or rotate phone numbers.
Note: The starter app includes a SQLite database with this table already created for you:
SQLite database tables
- Add a new helper function to the bottom of the
server/sms.jsfile to check how many SMS codes have been sent in the last 24 hours for a specificvisitorId:
server/sms.js
- Also update the existing
sendVerificationCodehelper function to accept and use thevisitorId:
server/sms.js
- Update
sendSMSto retrieve thevisitorId, and use it to check for an unusually high volume of recent SMS code requests made by the visitor and when saving a new code:
server/sms.js
This is a minimal example to show how to implement Fingerprint. In a real
application, make sure to implement proper security practices, error checking,
and actual SMS sending that align with your production standards.
7. Test your implementation
Now that everything is wired up, you can test the full verification flow.- Start your server if it isn’t already running and open http://localhost:3000:
Terminal
- Try requesting a code by entering a name and phone number and clicking Send code via SMS. You should see a success response.
- If you request SMS verification five times, additional attempts from the same device will be blocked based on the recent-code check. Open the page in incognito mode to see that you are still blocked since Fingerprint still recognizes your browser with the same visitor ID.
- While your demo is running, run the included headless bot test script from the
sms-pumpingfolder. This will attempt to trigger SMS sends using a headless browser, which will be flagged by the Bot Detection signal and rejected:
Terminal
Terminal