Identify users and add context
Out of the box, Shield shows each visit as a session with an IP, a device fingerprint, a risk score, and a recommendation. It does not know which of your users that session belongs to. This page shows how to tell it, so a high-risk session can be traced back to an account in your system.
How it works
You pass the raw values; the SDK hashes and encrypts them in the browser. Shield never sees a plain user ID or email in transit or in storage.
You give the SDK the visitor's user ID, email, and plan. Nothing is sent as given. In the browser (WebCrypto), before anything leaves the page, the SDK hashes the user ID and the lowercased email with SHA-256, and encrypts them with your site's identity key — an RSA-OAEP public key that Shield generates for every site and that the SDK fetches once per page. Every event then carries these fields:
| You provide | Shield receives |
|---|---|
userId | user_id_hash — SHA-256 of the ID, and user_id_enc — the ID encrypted with your site's key |
email | email_hash — SHA-256 of the lowercased address, email_domain (e.g. gmail.com), and email_enc — the address encrypted with your site's key |
plan | plan, as given |
salt (optional) | mixed into both hashes as SHA-256(salt + ':' + value) |
custom (optional, SDK 1.1.1+) | custom, as given — account facts such as account_tier or signup_channel, same rules as the custom object of track() below; shown in the dashboard's Visitor section. Never put PII here |
The ciphertext can only be opened by the Shield dashboard, which holds your site's private key (sealed at rest, never sent to the browser or stored next to your events in the clear). Shield's ingest and storage only ever see the hashes, the domain, the plan, and the ciphertext — never a plain email or user ID. Sites where identity reveal is switched off, and pages served over plain HTTP (no WebCrypto), send the hashes only.
Requires SDK 1.1.0 or later for the encrypted fields (1.0.9 for hashes); the v1.js CDN URL always serves the latest. Nothing changes in your snippet: the SDK fetches the key itself from https://shield.findip.net/v1/shield/identity-key. Pass identityKey to init (or data-identity-key on the script tag) with the key shown in your site's Settings to skip that request.
Google Tag Manager, official template
No code.
Open the FindIP Shield tag and expand Identify the visitor. Select the variables your site already exposes for logged-in users, typically the Data Layer Variables you use for GA4's user_id:
| Field | What to select |
|---|---|
| User ID | e.g. {{DLV - userId}} |
| Email address | e.g. {{DLV - userEmail}} |
| Plan | e.g. {{DLV - plan}} or a constant |
| Hash salt | an optional secret string |
If your site does not expose a user ID variable yet, ask your developer to push one to the dataLayer for logged-in users; it is the same variable GA4 uses.
Google Tag Manager, Custom HTML tag
Add identify to the init call with your GTM variables.
window.FindIP.init({
siteKey: 'pub_xxxxxxxxx',
identify: {
userId: '{{DLV - userId}}',
email: '{{DLV - userEmail}}',
plan: '{{DLV - plan}}',
custom: { account_tier: '{{DLV - accountTier}}', seats: '{{DLV - seats}}' }
}
});Numeric variables render as strings inside Custom HTML; the SDK turns numeric strings back into numbers.
GTM substitutes {{Variable}} references inside Custom HTML before the tag runs. Unset variables render as undefined and are ignored. The dashboard's Install page generates the full tag with the Identify logged-in visitors toggle enabled.
npm
import { init, identify } from '@findip/shield';
init({
siteKey: 'pub_xxxxxxxxx',
identify: {
userId: currentUser.id,
email: currentUser.email,
plan: currentUser.plan,
},
});
// Logged in after page load? Call identify() any time; pass null on logout.
identify({ userId: user.id, email: user.email });Script tag
Render the values into data-* attributes for the logged-in user.
<script
src="https://cdn.findip.net/shield/v1.js"
data-site-key="pub_xxxxxxxxx"
data-user-id="12345"
data-user-email="[email protected]"
data-plan="pro">
</script>An optional data-hash-salt attribute sets the salt. For pages where the user logs in without a reload, call FindIP.identify({ ... }) after login.
WordPress, WooCommerce, and Shopify
The official plugins send coarse page-type context only (product view, checkout view, order received) and never user identifiers. To identify users on those platforms, use the GTM template if you also run GTM, or call FindIP.identify({ ... }) from your theme for logged-in users.
More context per event
Identity set via identify is merged in automatically; fields passed to track() win when both are present.
FindIP.track('checkout_started', {
account_age_days: 412,
transaction_amount: 129.0,
currency: 'USD',
lead_source: 'google_ads',
custom: { cart_items: 3, coupon_applied: true }
});| Field | Type | Accepted values |
|---|---|---|
user_id_hash, email_hash | string | hash-shaped: hex 32–128 chars or base64 32+ chars. Set automatically by identify; pass directly only if you hash server-side |
user_id_enc, email_enc | string | fk1.<key id>.<base64> ciphertext under the site's identity key. Set automatically by identify; anything else is dropped |
email_domain | string | domain only, e.g. gmail.com |
plan, lead_source, form_name | string | up to 256 characters |
transaction_amount, account_age_days | number | any finite number |
currency | string | ISO 4217 code, uppercase, e.g. USD |
custom | object | up to 20 keys, key names up to 64 characters, values are strings up to 256 characters, numbers, or booleans |
Any other field is dropped. Inside custom, keys whose names contain password, card, cvv, ssn, secret, and similar are dropped, and any string value that looks like an email address, phone number, or card number is dropped, wherever it appears.
Sites with a dataLayer can also push user_id_hash, email_hash, email_domain, plan, transaction_amount, currency, form_name and lead_source before the SDK loads; automatic events pick up the first value found for each key. Values set through identify take precedence.
Where the identity appears
Open the site in the Shield dashboard. Events and Sessions both have a Visitor column showing the email (and user ID) of identified visitors; the event and session drawers have a Visitor section with the email, user ID, plan, domain and hashes. Sessions show the identity of their latest identified event, so a session that logged in halfway through is labelled too.
The Show visitor emails and user IDs switch in the site's Settings (on by default) controls the whole feature: switched off, the SDK stops sending encrypted values, anything already stored stays encrypted and the dashboard shows only the domain and hash. To find the account behind a hash, compute the same SHA-256 (with the same salt) of the value in your own system.
The rest of the context is under Raw payload (sanitized) in the event drawer as customer_context. Fields that were dropped are shown as null, which is the quickest way to check that a value passed validation.
Troubleshooting
user_id_hash is null in the payloadEither the value you passed was empty or undefined, or the page is not served over HTTPS. WebCrypto is only available in secure contexts; without it the SDK still sends plan and email_domain but skips the hashes and the encrypted values.@domain or a hashThe event carried no encrypted identity (SDK older than 1.1.0, plain-HTTP page, or "Show visitor emails and user IDs" was off when the event was sent), or the switch is off now. Check the site's Settings and the SDK version on the Install page.FindIP.identify() after the page events had already been sent. Pass identify to init instead, or set data-* attributes on the script tag.custom value is missingIt was longer than 256 characters, matched a sensitive pattern, or the object already had 20 keys.debug: true to the init options (or data-debug="true" on the script tag) to log each outgoing event and the resolved identity keys to the browser console.