Skip to content

Stop the same person taking a second free trial

One API call per signup. Allow, challenge or deny, before the account exists.

Start freeSee how it works

500 free decisions a month. No card.

One person in three disguises, each trying to claim the same free-trial ticket.

Where you read it back

Real screens from a live workspace, fed by the built-in sandbox.

OneTrial dashboard decision feed, showing signup decisions and their leading signals.
Every signup, the signal that drove it, where it came from.

Five kinds of evidence.
None decides alone.

A repeat device is a household. A shared subnet is an office. What moves a signup is how many independent kinds agree.

Five clues—a payment card, phone, envelope, globe and stopwatch—connected to one trial ticket with yellow paper strips.
The accounts a decision links to, and what each was told.
Payment

Card fingerprints from your own Stripe.

Device

A hint that survives cleared cookies.

Email

Plus tags stripped, dots folded, throwaways known.

Network

The subnet, the network type, where it resolves.

Behaviour

The pace of the form, a cold arrival, the hour.

Three answers, and what each one costs you

Abuse is rarely a bot. It is one person with a fresh mailbox and a cleared browser. Blocklists miss them; hard blocks lose real customers.

deny

Send them to a paid plan

Strong evidence of a repeat. Offer the paid plan. The visitor never sees why.

challenge

Ask for one proof

One proof: a card, an email link or a captcha. Real customers clear it and carry on.

allow

Create the trial

Nothing looks like a repeat. Most of your traffic, and it never notices.

Watch one signup become a verdictThe same call covers promo redemptions.
A customer holding her first free-trial ticket, framed in yellow.
your first week

Nobody gets turned away on your first day.

New workspaces start in shadow mode: every signup is scored, your app is always told allow. Read a week of what would have happened, then turn enforcement on.

json
{ "decision": "allow", "shadow": true, "wouldBe": "deny", "score": 81 }

An exception in plain English, editable before it saves.

A hand using a pencil to edit a paper rule, with a yellow sheet and a circled correction.

Three steps to live

Read the docs
  1. Put one tag on your signup page

    It mints a token and shows nothing. Nothing is decided in the browser.

    html
    <script src="https://onetrial.fly.dev/v1.js" data-key="YOUR_PUBLIC_KEY" async></script>
  2. Send the token with the signup

    One call from your server. Under 300ms at the median.

    typescript
    const res = await fetch('https://onetrial.fly.dev/api/v1/decisions', {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.ONETRIAL_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ visitorToken, email, ip, userId }),
    });
    const d = await res.json();
    if (d.decision === 'deny') return showPaidPlanOrSupport();
    if (d.decision === 'challenge') return redirect(d.challengeUrl); // trial unlocks on the challenge.completed webhook
    createTrial(userId);
  3. Act on one of three answers

    Allow, challenge or deny. The reasons are yours, never the visitor’s.

    json
    {
    "decision": "deny",
    "score": 81,
    "reasons": [
    "D_DEVICE_REPEAT",
    "E_EMAIL_REPEAT",
    "N_IP24_REPEAT"
    ],
    "challengeOptions": []
    }

Or let an agent do all three

Detects your framework, writes the env, injects the tag, self-tests the install.

  • Cursor
  • Claude Code
  • Copilot
  • v0
bash
npx onetrial init

Guides for Next.js, Express, Django, Rails and Laravel.

Everyone gets one.

500 free decisions a month. No card.

Start freeRead the docs
A hand offering one yellow free-trial ticket.