Account

API documentation / Recipes

Free trial abuse: one trial per person, not per email

A new email costs nothing, so a free month can be taken again and again. At sign-up, ask SpoofLens how many of your accounts this browser has opened in the last 30 days, and make the second and third ones prove they are new.

The problem

The pattern is easy to spot by hand: trials that never convert, sign-ups from the same few browsers, emails that differ only by a number or a dot. Asking every new user for a card or a phone number stops it, but it also stops honest people who only want to try the product.

This recipe keeps the trial open for a first account and adds a step only for later accounts from the same device and for browsers that do not look normal.

What the API gives you

seen.accounts_on_device_30dHow many of your accounts used this device in the last 30 days, this sign-up included. 1 means a new person. It needs user_ref, and the count stays inside your SpoofLens account: no other site sees it.
decision.actionWhat your rules say: allow, review or block. With the default rules, block means an automated browser or a score below 30, and review a score below 70.
device.idThe browser profile, the same on your site visit after visit. Keep it with the account: support, the block list and the referral recipe use it.

The code

It calls the helper (sl_check_visitor and sl_user_ref), which sends the browser data from the form, the IP address, the headers and a hash of the email.

PHP

<?php
// sign-up handler: decide what kind of trial the new account gets
$r = sl_check_visitor(sl_user_ref($_POST['email']));
$trial = 'start';                                     // no answer: start the trial as usual
$deviceId = '';
if ($r) {
    $accounts = $r['seen']['accounts_on_device_30d'] ?? 1;   // this sign-up included
    $action = $r['decision']['action'] ?? 'allow';           // what your rules say
    if ($action === 'block') {
        $trial = 'none';      // automation or a block list: no trial, offer the paid plan
    } elseif ($accounts >= 3) {
        $trial = 'card';      // a third account from this device in 30 days: trial with a card on file
    } elseif ($accounts === 2 || $action === 'review') {
        $trial = 'verify';    // a second account or a doubtful browser: confirm the phone or the email first
    }
    $deviceId = $r['device']['id'];   // save it with the account
}

Node.js

// sign-up handler (Express): decide what kind of trial the new account gets
const r = await slCheckVisitor(req, slUserRef(req.body.email));
let trial = 'start';                                  // no answer: start the trial as usual
let deviceId = '';
if (r) {
  const accounts = (r.seen && r.seen.accounts_on_device_30d) || 1;   // this sign-up included
  const action = (r.decision && r.decision.action) || 'allow';       // what your rules say
  if (action === 'block') trial = 'none';            // automation or a block list: no trial, offer the paid plan
  else if (accounts >= 3) trial = 'card';            // a third account from this device in 30 days: trial with a card on file
  else if (accounts === 2 || action === 'review') trial = 'verify';   // confirm the phone or the email first
  deviceId = r.device.id;                            // save it with the account
}

Python

# sign-up handler (Flask): decide what kind of trial the new account gets
r = sl_check_visitor(request, sl_user_ref(request.form['email']))
trial = 'start'                                      # no answer: start the trial as usual
device_id = ''
if r:
    accounts = (r.get('seen') or {}).get('accounts_on_device_30d', 1)   # this sign-up included
    action = (r.get('decision') or {}).get('action', 'allow')           # what your rules say
    if action == 'block':
        trial = 'none'       # automation or a block list: no trial, offer the paid plan
    elif accounts >= 3:
        trial = 'card'       # a third account from this device in 30 days: trial with a card on file
    elif accounts == 2 or action == 'review':
        trial = 'verify'     # a second account or a doubtful browser: confirm the phone or the email first
    device_id = r['device']['id']    # save it with the account

The numbers are a starting point. After a week, look at your request log: if second accounts from one device are mostly couples or colleagues who really are different people, move the extra step to the third account.

Soft steps instead of a ban

Rules in your account

Set them in Rules and alerts: the result comes back as decision in every answer, so your code does not change when the rules do.

Where it can be wrong

One line for your privacy policy

To prevent abuse of free trials, we check technical characteristics of your browser with SpoofLens (spooflens.com) when you sign up.

Limits

Other recipes

Get a free key 1,000 checks a month free, no card.