All articles Integration

When the requestId Is Missing: Ad Blockers, Script Failures, Signups

Every device-check integration ends up with the same question in a code review: what happens when the form arrives without a requestId? The browser was supposed to identify the visitor and send the id along. Sometimes it doesn’t. Handling that case is a policy decision with real consequences, and the safest default isn’t the obvious one.

Why the id goes missing

Honest and dishonest reasons both produce an empty field.

Honest reasons

  • Content blockers. Filter lists target scripts and endpoints that look like tracking. A third-party script host is an easy target, even when the purpose is fraud prevention.
  • Privacy signals. The Prynt agent honors Global Privacy Control by default. When navigator.globalPrivacyControl is set, identify() sends a minimal, non-tracking request and returns an empty requestId with consentWithheld: true. With respectDNT or requireConsent turned on, Do Not Track and a missing consent produce the same result.
  • Script failures. A slow mobile connection, a CSP that doesn’t allow the script origin, or a JavaScript error earlier on the page can all mean the agent never loads.
  • Timing. The user submits before the identification finishes, and the form code doesn’t wait.

Dishonest reasons

  • Deliberate stripping. Someone farming accounts by hand learns quickly that the field matters. Blocking the agent, or editing the request to drop the field, is the obvious next move.
  • Scripted signups. A bot that posts straight to your endpoint never runs the page’s JavaScript at all.
  • Forged or recycled ids. Sending a made-up string, or reusing one clean requestId for every account.

You can’t tell these apart from the empty field alone. The policy has to work without knowing which case it’s in.

Reduce the honest cases first

Before deciding what to do with missing ids, make fewer of them.

Serve the agent first-party. Load the agent and send API calls through your own domain, using a Cloudflare Worker or an nginx location, instead of a third-party host. Your proxy forwards the visitor’s IP to Prynt with the X-Prynt-Client-IP and X-Prynt-Proxy-Secret headers. The proxy secret comes from the console under API Keys → First-party proxy. Filter lists find it much harder to target a path on your own domain that also serves your app. The first-party serving guide has the setup.

Identify early, await on submit. Start identify() when the page loads and await the promise in the submit handler:

const ready = Prynt.load({ apiKey: 'pk_live_…', endpoint: 'https://example.com/prynt' })
  .then((a) => a.identify({ tag: { action: 'signup' } }))
  .catch(() => null);

form.addEventListener('submit', async (e) => {
  e.preventDefault();
  const r = await ready;
  form.pryntRequestId.value = (r && r.requestId) || '';
  form.submit();
});

The .catch(() => null) is deliberate. A failure in the fraud check must never stop an honest user from submitting the form. The server decides what an empty field means.

Allow the script in your CSP. If you use a Content-Security-Policy, add the agent’s origin to script-src and the API origin to connect-src. If you serve first-party, your own origin already covers both.

Why flag is the safer default

With the honest cases reduced, what’s left is a mix of privacy-conscious users, unlucky networks and abusers. The two obvious policies both fail:

  • Allow silently. The abuser simply strips the field and the device check disappears. The control becomes optional.
  • Block. Every user with GPC or a strict blocker is turned away. You’ve made “no JavaScript identification” mean “no account”. That’s a poor experience, and it’s hard to defend when someone asks why honoring a privacy signal cost them access.

The middle path is flag. Create the account, record that the device couldn’t be verified, and hold back whatever an abuser is actually after until the user does something that costs them per account:

Signup stateAccountTrial / creditsReview
Valid id, clean deviceCreatedUnlocked—
Valid id, device at limitCreated or refused per policyLocked until verifiedQueue
No id / unknown idCreatedLocked until phone or cardQueue
Id already linked to another accountRefused—Log

This is the default in Prynt’s integration recipes (Express, Clerk, Supabase, Auth0): onMissing: 'flag'. The recipe’s comment sums it up. Flag keeps privacy opt-outs and script blockers able to sign up, while block turns “no JS” into “no account”. The Express middleware walkthrough shows the full policy table.

The economics run in your favor. An honest user verifies once and moves on. An abuser who strips the field on every signup has to pass verification on every account. That’s the cost you wanted them to carry.

Treat forged and replayed ids like missing ones

A requestId in the form isn’t automatically a good one. The server looks it up with your secret key:

try {
  const event = await prynt.getEvent(requestId);
  if (event.linkedId) {
    // this identification is already attached to another account: a replay
  }
} catch (err) {
  if (err.status === 404) {
    // never issued for this environment: forged, or from another environment's key
  }
}

A 404 means the id was never issued to your environment, so treat it exactly like a missing id. An event whose linkedId is already set has been used for another account. That’s a replay, and a stricter response is fair. The recipes also treat identifications older than 15 minutes as missing, which limits how long a captured id stays useful. requestIds don’t expire on Prynt’s side, so that window is your own policy.

Watch the rate, not just the cases

Track the share of signups arriving without a valid id, by day and by acquisition channel. A stable low share is normal. A sudden jump on one channel, or a cluster of missing-id signups from the same hosting ASN, means someone has found the gap. That’s the moment to tighten that channel. Server-side signals such as datacenter IPs and TLS fingerprints still apply without the browser agent, as server-side vs client-side bot detection explains.

Wrap-up

Serve the agent first-party to cut down the honest cases. Look up every id you receive. Flag missing and unknown ids instead of failing the signup, and keep the expensive part of the free tier behind a step abusers have to repeat on every account. For the broader signup picture, see how to protect signups from fraud.

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.

Keep reading