Skip to main content

Add Fraud Protection to a React Website

This guide walks you through installing Opportify Fraud Protection on a React or Next.js website. Once set up, every form submission is analyzed for fraud signals in real time — bots, disposable emails, VPNs, and more.


Prerequisites

  • Access to the Opportify Admin Console
  • A React or Next.js project you can edit
  • Basic familiarity with JSX and fetch

How it works

Opportify's approach has two parts:

  1. The tracking script — loaded once in the page <head>. It silently observes browser signals and injects two hidden values into each of your forms: opportifyToken (a per-session risk token) and opportifyFormUUID (the identifier of the matched form endpoint).
  2. The submit endpoint — instead of POSTing to your own backend, your form sends data directly to https://api.opportify.ai/intel/v1/submit/<endpoint-id>. Opportify analyzes the submission, stores the result, and returns a JSON response your UI can act on.

Step-by-Step Setup

Step 1 — Open Opportify Admin and complete the Quick Start

Navigate to Quick Start and complete Steps 1 through 3.

Step 1 — Allowlist your domain. Enter your site's hostname (no https://, no trailing slash).

Example: for https://my-app.vercel.app enter my-app.vercel.app

Step 2 — Create a Form Endpoint. Click + New Endpoint, give it a descriptive name (e.g. Contact Form), and select a public key. Each endpoint maps to one form on your site.

Step 3 — Copy the Submit URL. From the endpoint list, copy the value in the Submit URL column. It looks like:

https://api.opportify.ai/intel/v1/submit/<your-endpoint-id>

Keep this URL handy — you will use it in Step 3 below.


Step 2 — Load the Opportify script

The script must be loaded once, globally, on every page of your app. Where you place it depends on your setup.

Next.js (Pages Router — _document.tsx)

Open (or create) pages/_document.tsx and add the script tag inside <Head>. Make sure to replace YOUR_PUBLIC_KEY with the key shown in the Opportify Admin Console.

// pages/_document.tsx
import Document, { Html, Head, Main, NextScript } from "next/document";

class MyDocument extends Document {
render() {
return (
<Html>
<Head>
{/* Opportify Fraud Protection — load before first user interaction */}
<script
src="https://cdn.opportify.ai/f/v0.4.3.min.js"
data-opportify-key="YOUR_PUBLIC_KEY"
async
></script>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}

export default MyDocument;

Next.js (App Router — layout.tsx)

// app/layout.tsx
import type { ReactNode } from "react";

export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html>
<head>
<script
src="https://cdn.opportify.ai/f/v0.4.3.min.js"
data-opportify-key="YOUR_PUBLIC_KEY"
async
></script>
</head>
<body>{children}</body>
</html>
);
}

Plain React — Vite (index.html)

For Vite-based projects the HTML entrypoint is index.html in the project root. Add the script tag inside <head>:

<!-- index.html (project root) -->
<head>
<!-- Opportify Fraud Protection — load before first user interaction -->
<script
src="https://cdn.opportify.ai/f/v0.4.3.min.js"
data-opportify-key="YOUR_PUBLIC_KEY"
async
></script>
</head>

Plain React — Create React App (public/index.html)

For CRA projects the HTML entrypoint is public/index.html. Add the script tag inside <head>:

<!-- public/index.html -->
<head>
<!-- Opportify Fraud Protection — load before first user interaction -->
<script
src="https://cdn.opportify.ai/f/v0.4.3.min.js"
data-opportify-key="YOUR_PUBLIC_KEY"
async
></script>
</head>
Where to find your public key

In the Opportify Admin Console, go to Settings → API Keys. The public key starts with pk_.


Step 3 — Connect your form to the Opportify endpoint

Replace your current form's fetch call so it POSTs to the Opportify Submit URL you copied in Step 1.

Before

const response = await fetch("/api/contact-form", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});

After

const response = await fetch(
"https://api.opportify.ai/intel/v1/submit/YOUR_ENDPOINT_ID",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}
);

Replace YOUR_ENDPOINT_ID with the UUID from the Submit URL you copied in Step 1.


Step 4 — Include the Opportify tokens in the payload

When the tracking script runs, it injects two hidden <input> fields into your form:

Field namePurpose
opportifyTokenPer-session risk token generated by the script
opportifyFormUUIDIdentifies which Form Endpoint matched this form

You need to read these values from the DOM and include them in the JSON body you send to the Submit URL. Add the following to your submit handler, before the fetch call:

const onSubmit = async (data: Inputs) => {
const payload = {
// ... your existing form fields ...
email: data.email,
message: data.message,

// Read Opportify tokens injected by the tracking script
opportifyToken:
document
.querySelector<HTMLInputElement>('input[name="opportifyToken"]')
?.value ?? "",
opportifyFormUUID:
document
.querySelector<HTMLInputElement>('input[name="opportifyFormUUID"]')
?.value ?? "",
};

const response = await fetch(
"https://api.opportify.ai/intel/v1/submit/YOUR_ENDPOINT_ID",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}
);

const result = await response.json();

if (!result.accepted) {
// Handle validation or API errors
console.error(result.errorMessage);
} else {
// Submission accepted
setSuccessMessage("Your message was sent successfully.");
}
};
Why read from the DOM?

The Opportify script injects opportifyToken and opportifyFormUUID as hidden inputs into the rendered HTML form — not into React state. Reading them with document.querySelector is the correct approach here.


Step 5 — Disable automatic interception on self-managed forms (optional)

By default, the Opportify script intercepts form submissions automatically. If your form already handles its own onSubmit (as shown above), add the data-opty-submit-interception="disable" attribute to the <form> element to prevent the script from intercepting the same submission twice:

<form
className="..."
data-opty-submit-interception="disable"
onSubmit={handleSubmit(onSubmit)}
>
{/* form fields */}
</form>
note

Skip this step if you want the script to handle submission automatically (e.g. for simpler forms with no custom submit logic).


Step 6 — Full example: Contact form

Below is a complete, minimal contact form integrating all the steps above. It uses react-hook-form for form state management — install it first if you haven't already:

npm install react-hook-form
# or: yarn add react-hook-form

This example uses react-hook-form for ergonomic form state and validation. To follow along exactly, install it in your project:

yarn add react-hook-form

You can also adapt this example to use your own form handling (for example, useState plus a custom onSubmit handler) if you prefer not to add this dependency.

import { useState } from "react";
import { useForm, SubmitHandler } from "react-hook-form";

type Inputs = {
name: string;
email: string;
message: string;
};

export function ContactForm() {
const { register, handleSubmit, formState: { errors } } = useForm<Inputs>();
const [successMessage, setSuccessMessage] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);

const onSubmit: SubmitHandler<Inputs> = async (data) => {
setIsSubmitting(true);
setErrorMessage("");

const payload = {
name: data.name,
email: data.email,
message: data.message,
// Opportify tokens — injected by the tracking script
opportifyToken:
document
.querySelector<HTMLInputElement>('input[name="opportifyToken"]')
?.value ?? "",
opportifyFormUUID:
document
.querySelector<HTMLInputElement>('input[name="opportifyFormUUID"]')
?.value ?? "",
};

try {
const response = await fetch(
"https://api.opportify.ai/intel/v1/submit/YOUR_ENDPOINT_ID",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}
);

const result = await response.json();

if (!result.accepted) {
setErrorMessage(result.errorMessage ?? "Something went wrong. Please try again.");
} else {
setSuccessMessage("Your message was sent successfully.");
}
} catch {
setErrorMessage("Network error. Please check your connection and try again.");
} finally {
setIsSubmitting(false);
}
};

return (
<form
data-opty-submit-interception="disable"
onSubmit={handleSubmit(onSubmit)}
>
<input {...register("name", { required: true })} placeholder="Your name" />
{errors.name && <span>Name is required</span>}

<input
type="email"
{...register("email", { required: true })}
placeholder="your@email.com"
/>
{errors.email && <span>Email is required</span>}

<textarea {...register("message", { required: true })} placeholder="Your message" />
{errors.message && <span>Message is required</span>}

{errorMessage && <p style={{ color: "red" }}>{errorMessage}</p>}
{successMessage && <p style={{ color: "green" }}>{successMessage}</p>}

<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Sending…" : "Send"}
</button>
</form>
);
}

Step 7 — Setup is complete

Your React form is now connected to Opportify Fraud Protection.

Return to the Opportify Admin Console and complete the remaining Quick Start steps to fine-tune your setup:

  • Step 4 — Data Retention: Choose how long submission data is kept.
  • Alerts: Configure email or in-app alerts for suspicious submissions.
  • Webhooks: Forward fraud signals and submission data to your own backend or third-party tools.

Viewing Form Submissions

After deploying, every form submission from your site will appear in the Form Submissions page of the Opportify Admin Console. For each submission you can see:

FieldDescription
Risk ScoreA numeric fraud risk rating assigned to the submission
Risk LevelA human-readable label: Low, Medium, High, or Critical
IP AddressThe originating IP of the submission
CountryGeo-location derived from the IP
EmailThe email address submitted, if collected
Submitted AtTimestamp of when the submission was received
Form EndpointWhich endpoint (and therefore which form) received the submission
Fraud SignalsIndividual signals that contributed to the risk score (e.g. disposable email, VPN detected, bot behavior)

You can filter submissions by risk level, date range, or endpoint to quickly identify and act on suspicious activity.


Troubleshooting

SymptomLikely causeFix
opportifyToken is always emptyScript not loaded or loaded after form renderEnsure the script tag is in <head> with async and the page has fully loaded before submission
CSP error in browser consoleMissing CSP directives for Opportify originsAdd https://cdn.opportify.ai to script-src and https://api.opportify.ai to connect-src in your Content Security Policy
Form submits twiceScript auto-interception active on a self-managed formAdd data-opty-submit-interception="disable" to the <form> element (Step 5)
404 on submitWrong endpoint ID in the URLDouble-check the Submit URL copied from the Opportify Admin Console