All articles Integration

Adding Fraud Signals to a PHP App

PHP still runs a large share of the web’s login forms, checkout pages, and account dashboards, which makes it a natural place to add fraud signals. The mistake most teams make is trying to make the trust decision in the browser, where any value can be edited. The device data is collected client-side, but the decision has to happen in PHP, on the server, where the attacker cannot reach.

This guide walks through wiring device intelligence into a PHP application end to end: loading the agent, verifying the result server-side, and turning the response into a decision at login and checkout. The examples use plain PHP so they transfer cleanly to Laravel, Symfony, or a legacy codebase.

The client and server split

The division of labor is the whole design. The browser runs a small agent that computes a device fingerprint and collects signals; it returns an opaque token or a sealed result. Your PHP backend takes that token, verifies it, and reads the trustworthy signals from the verified payload. Nothing the client sends is trusted until the server has confirmed it.

  • Client: loads the agent, obtains a fingerprint and a request token, attaches the token to the form submission.
  • Server (PHP): verifies the token, extracts the device ID and signals, applies your rules, and decides.

This mirrors the reasoning in server-side vs client-side bot detection: client-side collection is fine, client-side trust is not. The pattern generalizes across languages, and you will find the same split in node server-side verification.

Loading the agent in the browser

On the pages where you need a signal, load the agent and pass the resulting token along with the form. Keep this minimal; the agent does the collection.

<script src="/prynt/agent.js"></script>
<script>
  Prynt.load().then(async (client) => {
    const result = await client.get();
    document.getElementById('pryntToken').value = result.requestToken;
  });
</script>
<form method="post" action="/login">
  <input type="hidden" id="pryntToken" name="prynt_token">
  <!-- email, password fields -->
</form>

The hidden field carries the request token into your PHP handler. The token is a reference the server exchanges for the full, verified result; it is not itself the sensitive data, which is why it is safe to place in a form. For the mechanics of serving the agent from your own origin, see first-party agent serving.

Verifying the result server-side

In PHP, exchange the token for a verified result and read the signals from that response, never from anything the browser asserted directly.

<?php
function verifyDevice(string $token): ?array
{
    $ch = curl_init('http://127.0.0.1:5050/v1/verify');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . getenv('PRYNT_SERVER_KEY'),
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode(['requestToken' => $token]),
        CURLOPT_TIMEOUT => 3,
    ]);
    $body = curl_exec($ch);
    if ($body === false || curl_getinfo($ch, CURLINFO_HTTP_CODE) !== 200) {
        return null; // fail open or closed per your policy
    }
    return json_decode($body, true);
}

The returned payload includes a stable device ID, a confidence score, and Smart Signals such as bot, VPN, and incognito. If you are running Prynt with sealed results, you can decrypt and verify the payload locally with your key instead of a round trip, which removes a network hop for latency-sensitive paths.

Scoring a login and a checkout

Verification gives you signals; the value comes from acting on them. Handle the null case explicitly, then branch on the device history and score.

$result = verifyDevice($_POST['prynt_token'] ?? '');

if ($result === null) {
    // Decide your posture: step up rather than hard-fail.
    return challenge();
}

$deviceId  = $result['deviceId'];
$score     = $result['suspectScore'];   // higher = riskier
$isBot     = $result['signals']['bot'] ?? false;
$knownHere = deviceSeenForUser($deviceId, $userId); // your DB lookup

if ($isBot || $score >= 80) {
    return challenge();          // proof-of-work or step-up auth
}
if (!$knownHere && $score >= 40) {
    return requireEmailVerification();
}
proceed();

The deviceSeenForUser lookup is your own table linking device IDs to accounts; it is what powers new device login detection. At checkout you would weight payment fraud device signals more heavily and add velocity checks, but the shape is identical: verify, enrich with history, decide.

Hardening the integration

A few practices keep this robust in production:

  • Decide your fail mode. If verification is unavailable, do you fail open (allow, log) or closed (challenge)? Pick per endpoint; a marketing page and a withdrawal deserve different answers.
  • Bind tokens to actions. A token minted for a login should not be replayable at checkout. Check freshness and, where supported, the action context.
  • Rate limit by device, not just IP. The verified device ID is a better key than an address behind NAT or a proxy; see rate limiting by device.
  • Log the reason codes. Store the reason codes behind each decision so support can explain and reverse a false positive.

Keep the server key in environment configuration, never in the repository, and restrict the verification endpoint to your backend network. The client should never hold a key that can read verified results.

Frequently asked questions

Can I do fraud detection entirely in PHP?

The device signals are collected in the browser by a JavaScript agent, but the trust decision belongs on the server. PHP is where you verify the result, look up the device history, and score the request, so the client can never fake a passing grade.

Does adding fraud signals slow down my PHP app?

Verification is a single server-side lookup that typically adds a few milliseconds, and it happens only at sensitive actions like login or checkout. It does not run on every page, so the overhead is negligible.

Adding fraud signals to PHP is less about new infrastructure than about drawing the trust boundary correctly: collect in the browser, decide on the server, and enrich with your own device history. Start with login, extend to checkout, and let the verified signals drive proportionate responses. The SDKs and docs cover the full API, and protect login form fingerprinting walks through the login case in more depth.

Run it yourself

Prynt is open-source, self-hostable device intelligence — visitor IDs, bot & fraud Smart Signals, and behavioral biometrics you own end to end.

Keep reading