Remix leans hard on server actions, which is exactly where you want a fraud decision to happen. Adding device intelligence gives every action a verifiable visitor ID and bot score, so a scripted POST no longer looks like a real user.
This guide adds Prynt to Remix cleanly: load the agent in the browser, attach the event ID with useFetcher, and verify inside the action. The approach fits Remix’s data model naturally, since actions already own every mutation and are the one place all writes funnel through.
Load the agent client-side
The agent runs only in the browser. Load it once in a top-level component or a small hook so it is ready before the user submits.
import { useEffect, useRef } from 'react'
import Prynt from '@prynt/js'
export function usePrynt() {
const agent = useRef(null)
useEffect(() => {
Prynt.load({
apiKey: window.ENV.PRYNT_KEY,
endpoint: 'https://api.pryntid.com'
}).then(a => { agent.current = a })
}, [])
return agent
}
Expose your public key through Remix’s window.ENV pattern so the browser can read it.
Attach the event ID on submit
Call identify() when the form submits and pass the requestId through the fetcher so it arrives in the action.
const agent = usePrynt()
const fetcher = useFetcher()
async function onSubmit(e) {
e.preventDefault()
const result = await agent.current.identify()
const form = new FormData(e.currentTarget)
form.set('pryntId', result.requestId)
fetcher.submit(form, { method: 'post' })
}
The browser only collects the signal here; the action makes the call.
Verify inside the action
The action looks up the event with your secret key and branches on the result. This is the trustworthy decision point.
// route action
export async function action({ request }) {
const form = await request.formData()
const id = form.get('pryntId')
const res = await fetch(
`https://api.pryntid.com/v1/events/${id}`,
{ headers: { Authorization: `Bearer ${process.env.PRYNT_SECRET}` } }
)
const event = await res.json()
if (event.confidence < 0.5) {
return json({ error: 'verification required' }, { status: 401 })
}
// store event.visitorId with the account
return json({ ok: true })
}
Persist event.visitorId against the account so the same device behind different logins becomes visible over time. The field reference lives in the server-side verification docs.
Because Remix supports non-JavaScript form posts, an action can receive a submission with no pryntId. Handle that deliberately: require the field on high-value actions like registration and payouts, and reject submissions that arrive without a verifiable event. A missing ID is itself a signal worth logging, since bots frequently disable scripts to slip past client-side checks. Keeping the verification in the action, rather than a loader, ensures it runs on every write path and cannot be bypassed by requesting a page directly.
Add Form Shield for behavioral signals
To catch scripted fills that produce a clean POST, guard the form element directly.
agent.current.protectForm(formRef.current, {
autoGuard: true,
expectedScripts: ['latin']
})
expectedScripts flags submissions in unexpected writing systems, a cheap and reliable spam tell for Latin-script audiences. It complements server verification rather than replacing it.
Branch, do not hard-block
Because Remix actions already own your write logic, they are the natural place to route on the score: humans pass, low-confidence submissions get a challenge or email verification. Prynt is a managed cloud service, so geolocation, ASN, proxy, and bot signals arrive with no infrastructure to run, and the free tier covers early traffic.
Add a key, wire the fetcher, and verify in the action. See the pricing and free plan to start, and your Remix app will make fraud decisions on data a bot cannot forge.
Try it free
Prynt is device intelligence with a free tier — visitor IDs, bot & fraud Smart Signals, and behavioral biometrics, powered by a cross-site network. Start free.