Every referral program gets the same fraud engine, on every plan. It looks at each click, signup and sale as it arrives, adds up what it saw into a score from 0 to 100, and holds money when the score is high. It never rejects a sale, never bans a promoter and never confirms fraud by itself: a person decides, in the dashboard's Fraud tab or through the API, and every decision is recorded in the activity log.
What the score does
| Score | What happens |
|---|---|
| Under the hold line (40 by default) | Nothing. The commission waits out the program's usual hold. |
| From the hold line | The commission (and any reward that is not cash) waits 14 more days, so a refund or a chargeback has time to arrive. |
| From the review line (70 by default) | The money is held until a person decides, as a risk_review flag in the review queue. |
| Self-referral, a shared card, or payout details just changed | Always asked, whatever the score. |
Each commission says why it waits (risk in the API: the score and every reason as a
sentence), on the Commissions tab beside its date. The promoter sees the later date, and
"Under review" while it waits for you; never why.
The lines, the extra days and the rest of the settings below are the program's own:
change them on the program's Settings tab, under Fraud, or with
PATCH /v1/referrals/programs/{id}/risk-policy. A change applies
from the next conversion; what was already assessed keeps its hold.
What it looks at
Each rule that counts adds its weight and a sentence a person can check: what was seen, in counts and times, never an address, a card or an email.
| Rule | What it means |
|---|---|
| Self-referral | The customer is the promoter: the same email, the same user in your app, or the same card. |
| Too fast after the click | A signup or sale came sooner after the click than a person could manage (5 seconds for a signup, 15 for a sale, 30 for a shop order, by default). |
| Paid search | The click came from a paid ad on a program that does not allow bidding on your brand, unless its campaign id is on your allowlist. |
| Data-centre click | The click came from a cloud or hosting network, not a home or a phone. |
| Codes without clicks | A promoter's sales keep arriving through a discount code nobody clicked a link for. |
| Code leak | One code is redeemed far more often than its links are clicked, usually because it was posted on a coupon site. |
| Look-alike addresses | The customer's email is one or two characters from other customers' in the program. |
| Throwaway email | The customer signed up with a disposable address. |
| Unusual conversion rate | A promoter converts far below or far above everyone else in the program. |
| Refunds | A high share of a promoter's sales are refunded. |
| New promoter | The promoter joined in the last 90 days, and their earnings wait a little longer while "Hold new partners longer" is on, as it is unless you switch it off. |
A click flood from one address stops being counted at all (60 a minute by default), and the redirect still answers at once: no check ever slows a visitor down.
Residential proxies are not caught by any list. They look like homes because they are homes. The time from click to conversion, the conversion-rate outliers and the velocity per address and device are what catch them.
The click id
The website tag keeps a click id (bc_click, 16 letters and digits) beside the referral
code, and BoltClawReferral.clickId() returns it, so the engine can tell how long a customer took
from the click to the conversion. Pass it on where you pass the code:
- Stripe Checkout:
client_reference_idas<code>--<click id>, which is whatBoltClawReferral.clientReferenceId()gives (Payment Links and the Buy Button get it from the tag by themselves), ormetadata.boltclaw_clickbesidemetadata.boltclaw_ref. A subscription checkout's click is kept with the customer, so the first invoice is timed from it. - Any other billing system: a
boltclaw_clickfield in the same custom data that carriesboltclaw_ref.BoltClawReferral.attach(form)adds both fields to a form. - The API:
referral.click_idonPOST /v1/referrals/conversions.
Without it nothing breaks; the timing rule simply has nothing to measure.
data-clean-url on the tag removes via and bc_click from the address bar once the tag
has read them, so a link somebody copies carries neither, and nor does anything that reads the
address after the tag. A router that writes back the address it was rendered with (Next.js's
App Router does, as it hydrates) is undone once the page has loaded. Analytics that read the
address before that still see both: give them the address without via and bc_click in
their own settings (page_location for Google Analytics, beforeSend for Vercel Analytics).
The card check
With Stripe, add the charge.succeeded event to the webhook endpoint (the Bolt Claw
Stripe App receives it already). Bolt Claw turns the card's fingerprint into a keyed hash
for your workspace the moment it arrives and keeps only that, so it can say "this card
paid for 10 of one promoter's customers" without ever storing or showing the card. Without
the event every other rule still runs.
The review queue
The Fraud tab, and GET /v1/referrals/programs/{id}/flags, list
what waits for a decision, filtered by rule, promoter, score and age. Each flag shows the
score and every reason, a timeline of the customer's click, signup, sale and refunds with
the seconds between them, and the promoter's history in the program.
- Dismiss: not fraud. What it held continues to a payout.
- Confirm: fraud. What it held becomes
fraudand is never paid; ban the promoter only if you also ask for it. - Hold this promoter's payouts: a stop while you look. Nothing is taken away.
Dismiss and confirm work on up to 200 flags at once with one reason, each recorded on its own. An AI assistant must always ask a person before it dismisses or confirms a flag, or changes the settings.
Promoters never see why. Their side says "Under review" and nothing else, so nobody learns which rule to dodge.
Alerts
referral.risk.alert is sent to your webhooks when a promoter first
needs a look or a code leak starts, at most once per promoter a day. Switch on the alert
email on the Fraud settings to have the program's managers emailed too.
Protecting a promoter's payouts
Changing where a promoter is paid needs a sign-in within the last 15 minutes (and the
second step, if their account has one). After any change their payouts wait 48 hours, the
payout preview says "Payout details changed 3 hours ago", and their account's address gets
an email with a link that locks their payouts without signing in, in case it was not them.
Your own users' details, set through the in-app panel, raise
referral.referrer.payout_details_changed instead, so you can tell them.
Applications
Ask up to three questions on the program's join page with application_questions. Each
application shows the answers, the promoter's website, how old their account is, and two
counts: near-identical applications sent to other programs this week ("Looks templated"),
and applications from one network and device in a day. Never which programs, which
accounts or an address.
What is never stored
No visitor's IP address, no card fingerprint and no customer email is stored in the clear or shown to anyone, hashed or not. Addresses are looked up against published lists of cloud and hosting networks (X4BNet, Amazon Web Services, Google Cloud and a fixed set of hosting networks), imported into our own database once a week; no visitor is ever sent to a third party, and no third-party script runs on a referral link.