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 CHALLENGEStripe (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
| Signal | Meaning |
|---|---|
payment.fingerprint_reuse / cluster.shared_card | Same card linked to another account |
velocity.card_fingerprint | Same card used too often in 24h / 7d |
payment.prepaid / payment.virtual | Funding type flags you send (or BIN heuristics on analyze) |
Reason codes include CARD_FINGERPRINT_REUSE, CLUSTER_SHARED_CARD, HIGH_VELOCITY_CARD.
Product trade-off
| Approach | Conversion | Abuse resistance |
|---|---|---|
| No card at trial | Higher | Relies on email / device / IP (cheap to rotate) |
| Card stored, not charged | Lower | One 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.

