---
title: Fraud detection with Ephemeral IDs
description: Learn how to implement fraud detection using Turnstile's Ephemeral IDs to identify and block bad actors who rotate IP addresses.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/turnstile/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Fraud detection with Ephemeral IDs

[Ephemeral IDs](https://developers.cloudflare.com/turnstile/additional-configuration/ephemeral-id/) let you detect fraud patterns that evade traditional IP-based detection. This tutorial will show you how to log Ephemeral IDs, detect suspicious patterns, and block bad actors.

Attackers often create hundreds of fake accounts to abuse promotions, rotate through proxy pools to avoid IP-based rate limiting, and use real browsers to evade basic bot detection.

Traditional IP-based detection fails because each request appears to come from a different address. Ephemeral IDs solve this by identifying the underlying client device, even when IP addresses change.

## Before you begin

* Ephemeral IDs require [Enterprise Bot Management](https://developers.cloudflare.com/bots/plans/bm-subscription/) with the [Enterprise Turnstile add-on](https://developers.cloudflare.com/turnstile/plans/), or [standalone Enterprise Turnstile](https://developers.cloudflare.com/turnstile/plans/). Contact your account team to enable this feature.
* You must have basic familiarity with Turnstile integration. Refer to [Get started with Turnstile](https://developers.cloudflare.com/turnstile/get-started/) for more information.

Ephemeral IDs are not guaranteed to be unique

Not every unique device will produce a unique Ephemeral ID. Privacy-focused devices and browsers (such as iPhones and Safari) limit the signals available for fingerprinting, which means multiple legitimate users may share the same Ephemeral ID.

Use Ephemeral IDs to detect high-volume abuse patterns, not to uniquely identify individual devices. Always set detection thresholds high enough to avoid false positives.

---

### Set up logging

Create a table to store events with Ephemeral IDs.

```
CREATE TABLE turnstile_events (    id              BIGSERIAL PRIMARY KEY,    ephemeral_id    VARCHAR(64) NOT NULL,    event_type      VARCHAR(50) NOT NULL,  -- 'signup', 'login', 'checkout'    ip_address      VARCHAR(45),    user_id         VARCHAR(128),          -- NULL for signups, populated after    created_at      TIMESTAMPTZ DEFAULT NOW());
CREATE TABLE blocked_ephemeral_ids (    ephemeral_id    VARCHAR(64) PRIMARY KEY,    reason          VARCHAR(255),    created_at      TIMESTAMPTZ DEFAULT NOW());
```

### Extract and log the Ephemeral IDs

When you call Siteverify, the Ephemeral ID is returned in the `metadata` field. Log it with every protected action.

TypeScript

```
async function verifyAndLogTurnstile(  token: string,  ip: string,  secretKey: string,  eventType: string,  db: Database,): Promise<{ success: boolean; ephemeralId?: string; isBlocked: boolean }> {  // Call Siteverify API  const response = await fetch(    "https://challenges.cloudflare.com/turnstile/v0/siteverify",    {      method: "POST",      headers: { "Content-Type": "application/x-www-form-urlencoded" },      body: new URLSearchParams({        secret: secretKey,        response: token,        remoteip: ip,      }),    },  );
  const result = await response.json();
  if (!result.success) {    return { success: false, isBlocked: false };  }
  const ephemeralId = result.metadata?.ephemeral_id;
  if (ephemeralId) {    // Log the event    await db.query(      `INSERT INTO turnstile_events (ephemeral_id, event_type, ip_address)       VALUES ($1, $2, $3)`,      [ephemeralId, eventType, ip],    );
    // Check if already blocked    const blocked = await db.query(      `SELECT 1 FROM blocked_ephemeral_ids WHERE ephemeral_id = $1`,      [ephemeralId],    );
    if (blocked.rows.length > 0) {      return { success: true, ephemeralId, isBlocked: true };    }  }
  return { success: true, ephemeralId, isBlocked: false };}
```

### Use the Ephemeral ID in your sign up flow

TypeScript

```
export async function handleSignup(request: Request, env: Env) {  const formData = await request.formData();  const email = formData.get("email") as string;  const turnstileToken = formData.get("cf-turnstile-response") as string;  const ip = request.headers.get("CF-Connecting-IP") || "";
  // Verify Turnstile and log the Ephemeral ID  const verification = await verifyAndLogTurnstile(    turnstileToken,    ip,    env.TURNSTILE_SECRET_KEY,    "signup",    env.DB,  );
  if (!verification.success) {    return new Response("Verification failed", { status: 400 });  }
  // Block if this device is flagged  if (verification.isBlocked) {    // Return a generic message - don't reveal detection    return new Response("Please verify your email to continue", {      status: 202,    });  }
  // Proceed with normal signup  const userId = await createUser(email, formData.get("password"));
  // Update the log with the new user ID  if (verification.ephemeralId) {    await env.DB.query(      `UPDATE turnstile_events       SET user_id = $1       WHERE ephemeral_id = $2 AND event_type = 'signup' AND user_id IS NULL       ORDER BY created_at DESC LIMIT 1`,      [userId, verification.ephemeralId],    );  }
  return new Response("Account created", { status: 201 });}
```

### Detect fraud patterns

Run the following query periodically (for example, every five minutes) to find suspicious Ephemeral IDs:

```
-- Find devices creating multiple accounts in the last hourSELECT    ephemeral_id,    COUNT(*) as signup_count,    COUNT(DISTINCT ip_address) as unique_ipsFROM turnstile_eventsWHERE    event_type = 'signup'    AND created_at > NOW() - INTERVAL '1 hour'GROUP BY ephemeral_idHAVING COUNT(*) > 3;  -- More than 3 signups = suspicious
```

When you find suspicious IDs, block them:

```
INSERT INTO blocked_ephemeral_ids (ephemeral_id, reason)SELECT    ephemeral_id,    'Multiple signups: ' || COUNT(*) || ' in 1 hour'FROM turnstile_eventsWHERE    event_type = 'signup'    AND created_at > NOW() - INTERVAL '1 hour'GROUP BY ephemeral_idHAVING COUNT(*) > 3ON CONFLICT (ephemeral_id) DO NOTHING;
```

### Investigate and take action

When you ban accounts for abuse, find other accounts from the same device:

```
-- Find all accounts created from the same device as a banned userSELECT DISTINCT te2.user_id, te2.created_atFROM turnstile_events te1JOIN turnstile_events te2 ON te1.ephemeral_id = te2.ephemeral_idWHERE te1.user_id = 'BANNED_USER_ID'  AND te2.user_id IS NOT NULL  AND te2.user_id != 'BANNED_USER_ID';
```

Bulk-flag accounts for review:

```
-- Flag all accounts from a suspicious deviceUPDATE usersSET status = 'under_review'WHERE id IN (    SELECT DISTINCT user_id    FROM turnstile_events    WHERE ephemeral_id = 'x:SUSPICIOUS_ID_HERE'      AND user_id IS NOT NULL);
```

---

## Recommendations

Privacy

Ephemeral IDs are privacy-preserving. They are scoped to your account, short-lived, and cannot identify individuals across sites.

* **Log immediately**: Capture the Ephemeral ID right when you call Siteverify.
* **Silent rejection**: When blocking fraud, return generic errors. Never reveal that you detected the device.
* **Tune thresholds**: Start conservative (for example, three sign ups per hour) with the query and adjust based on your traffic.
* **Combine signals**: Use Ephemeral IDs alongside IP reputation and behavior analytics.

---

## Related resources

* [Ephemeral IDs](https://developers.cloudflare.com/turnstile/additional-configuration/ephemeral-id/)
* [Server-side validation](https://developers.cloudflare.com/turnstile/get-started/server-side-validation/)
* [Integrate Turnstile, WAF, and Bot Management](https://developers.cloudflare.com/turnstile/tutorials/integrating-turnstile-waf-and-bot-management/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/turnstile/tutorials/fraud-detection-with-ephemeral-ids/#page","headline":"Fraud detection with Ephemeral IDs · Cloudflare Turnstile docs","description":"Learn how to implement fraud detection using Turnstile's Ephemeral IDs to identify and block bad actors who rotate IP addresses.","url":"https://developers.cloudflare.com/turnstile/tutorials/fraud-detection-with-ephemeral-ids/","inLanguage":"en","image":"https://developers.cloudflare.com/core-services-preview.png","dateModified":"2026-05-05","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"},"keywords":["JavaScript","Node.js","SQL","Account takeover"]}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/turnstile/","name":"Turnstile"}},{"@type":"ListItem","position":3,"item":{"@id":"/turnstile/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/turnstile/tutorials/fraud-detection-with-ephemeral-ids/","name":"Fraud detection with Ephemeral IDs"}}]}
```
