AntiTrial

Card on trial start

Store a Stripe payment method without charging, then score card.fingerprint for cross-account abuse.

Emails, IPs, and devices are cheap to rotate. Working cards are not. Stripe exposes a stable PaymentMethod.card.fingerprint that matches the same underlying card across different Customer records — even when email, IP, and device all differ.

AntiTrial does not collect cards for you. Your app stores a payment method via Stripe (SetupIntent / Payment Element, no charge), then sends the fingerprint on evaluate(). AntiTrial correlates reuse across accounts.

Asking for a card up front lowers trial conversion. Use this when the abused resource is expensive (inference, compute, credits). Skip it for cheap-to-serve trials and rely on email/device/velocity instead.

Flow

1. User starts trial
2. Stripe SetupIntent → PaymentMethod saved (not charged)
3. Backend reads paymentMethod.card.fingerprint
4. evaluate({ user, signals: { payment: { cardFingerprint } } })
5. Grant credits only on ALLOW / resolved CHALLENGE

Stripe (store, do not charge)

// After SetupIntent succeeds — server-side with your Stripe secret
const pm = await stripe.paymentMethods.retrieve(paymentMethodId);
const cardFingerprint = pm.card?.fingerprint;
const prepaid = pm.card?.funding === 'prepaid';

evaluate with fingerprint

curl -X POST https://api.antitrial.com/v1/risk/evaluate \
  -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "trial.signup",
    "user": { "id": "user_abc" },
    "context": {
      "email": "[email protected]",
      "installationId": "inst_…"
    },
    "signals": {
      "payment": {
        "cardFingerprint": "Xt5EWLLDS7FJjR1c",
        "prepaid": false,
        "virtual": false
      }
    }
  }'

Always pass user.id so AntiTrial can tell same card, different accounts apart from a repeat visit by the same user.

Node SDK

import { createAntiTrial } from '@antitrial/node';

const client = createAntiTrial({ secretKey: process.env.ANTITRIAL_SECRET_KEY });

const result = await client.evaluate({
  event: 'trial.signup',
  user: { id: userId },
  context: { email, installationId },
  signals: {
    payment: {
      cardFingerprint: paymentMethod.card.fingerprint,
      prepaid: paymentMethod.card.funding === 'prepaid',
    },
  },
});

if (result.action === 'BLOCK') {
  // Same card already used on another trial account
}

Standalone analyze (optional)

curl -X POST https://api.antitrial.com/v1/payment/analyze \
  -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "cardFingerprint": "Xt5EWLLDS7FJjR1c",
    "accountRef": "user_abc",
    "prepaid": false
  }'

Prefer evaluate() for gating credits — it combines card reuse with email, device, velocity, and cluster signals.

Signals you get

SignalMeaning
payment.fingerprint_reuse / cluster.shared_cardSame card linked to another account
velocity.card_fingerprintSame card used too often in 24h / 7d
payment.prepaid / payment.virtualFunding type flags you send (or BIN heuristics on analyze)

Reason codes include CARD_FINGERPRINT_REUSE, CLUSTER_SHARED_CARD, HIGH_VELOCITY_CARD.

Product trade-off

ApproachConversionAbuse resistance
No card at trialHigherRelies on email / device / IP (cheap to rotate)
Card stored, not chargedLowerOne fingerprint catches cross-account farms

AntiTrial scores the fingerprint when you send it. Whether to require a card is a product decision for your SaaS.

On this page