Two facts only your site can know, and Loghound will not invent either: who the visitor is, and whether they were signed in. Your site passes them to the beacon.
The identity must be a valid email address, at most 128 bytes. Anything else (a customer number, a display name, a username) is dropped and never stored. A visitor counts as signed in only when a valid email arrived; a signed-in flag without one is recorded as not signed in.
Loghound never guesses either value. No cookie is read, no form is scraped, no meta tag is looked for, no variable is hunted through. If your site does not say, the field does not exist on the session.
The normal case. The template that renders the page renders the tag, in the same response: no second request, no extra script, no ordering problem.
<script src="https://loghound.example.com/b.js?v=1"
data-ident="ada@example.com" data-signed-in="1" defer></script>You would not write the address literally, of course — it comes out of whatever object your framework already has.
WordPress
<?php
// wp-content/mu-plugins/loghound.php - a must-use plugin, so it survives a theme change.
add_action('wp_head', static function (): void {
$attrs = ' data-signed-in="0"';
if (is_user_logged_in()) {
$attrs = ' data-ident="' . esc_attr(wp_get_current_user()->user_email) . '"'
. ' data-signed-in="1"';
}
echo '<script src="https://loghound.example.com/b.js?v=1"' . $attrs . ' defer></script>' . "\n";
}, 99);A signed-out visitor gets data-signed-in="0" and no data-ident at all — which is right: they are anonymous, and that is a different statement from “we were not told”. esc_attr() is what stops an address containing a quote from breaking the tag.
Writing the "0" explicitly is the clearest form, but you do not have to restructure a template to get it. An attribute that is present but empty is also an answer, and the answer is no: data-signed-in="<?= $user->id ?>" in a template that renders nothing for a guest sends data-signed-in="", and Loghound reads that as anonymous rather than as silence. Only leaving the attribute out altogether means “we were not told”. The same holds for data-ident: an empty one attaches no identity.
WP Rocket, W3 Total Cache, LiteSpeed and Cloudflare APO all cache the rendered tag with the page, so the first visitor’s identity would be served to everybody else. Exclude logged-in users from the cache — every one of those plugins does so by default — or use route 3 below from an uncached request.
Drupal
<?php
# your_theme.theme (or a small custom module)
function your_theme_page_attachments(array &$attachments): void {
$account = \Drupal::currentUser();
$tag = [
'#type' => 'html_tag',
'#tag' => 'script',
'#attributes' => [
'src' => 'https://loghound.example.com/b.js?v=1',
'defer' => TRUE,
'data-signed-in' => $account->isAuthenticated() ? '1' : '0',
],
];
if ($account->isAuthenticated()) {
$tag['#attributes']['data-ident'] = $account->getEmail();
}
$attachments['#attached']['html_head'][] = [$tag, 'loghound'];
$attachments['#cache']['contexts'][] = 'user';
}html_head rather than a library, because a library is declared once in YAML and cannot carry a value that changes per request. The user cache context is not optional: without it Drupal’s render cache serves the first authenticated visitor’s address to every other one. Use and run drush cr afterwards. Keep getEmail(): a username is not an email address and would be dropped.
They must be set before b.js executes, which with defer means anywhere in the document.
<script>window.LoghoundIdent = "ada@example.com"; window.LoghoundSignedIn = true;</script> <script src="https://loghound.example.com/b.js?v=1" defer></script>
In Google Tag Manager that is a Custom HTML tag with data layer variables, because a tag manager runs in the browser and cannot know who is signed in — the value has to reach it from your own page:
<script>
window.LoghoundIdent = "{{Loghound Ident}}";
window.LoghoundSignedIn = "{{Loghound Signed In}}";
</script>
<script src="https://loghound.example.com/b.js?v=1" defer></script>A single-page application that signs somebody in without a navigation, which no attribute can express:
window.loghound.identify('ada@example.com', true);It makes no request of its own: the values ride the heartbeat that is already scheduled, so attaching an identity costs your site nothing extra. Both arguments are optional, but a visitor is only recorded as signed in when the first one is a valid email address.
- Signed in: a valid email address arrived.
- Anonymous: your site said the visitor was not signed in, or claimed signed in without a valid email.
- Not reported: your site sent neither. The field is left out, and the panel never counts it as anonymous.
Figure 1 — three states. The split between signed-in and anonymous traffic is the reason the field exists; counting “not reported” as anonymous would make that split a fabrication over traffic nobody classified.
Where several payloads in one session disagree, the resolution follows what happened: the last non-empty identity wins, because somebody who signs in halfway through a visit is that person by the end of it; and signed-in beats anonymous, because a session that was authenticated at any point was an authenticated session.
- The email address is kept whenever your site sends a valid one. There is no switch for it: nobody puts a visitor’s email into a template by accident, so a site that sends one wants it kept.
- The signed-in yes or no is kept by default. Setting
beacon.store_signed_intofalsein the configuration drops it; it never drops the email.
Once sent, it is written to the visit, appears in the panel and on Signed-in visitors, lives in your Opensolr index and in every backup of it until retention deletes the visit. If you do not want it stored, do not send it. GDPR covers what to tell your visitors.
A signed-in visitor is scored human unless the browser was driven by software or its own headers contradict it; see How a visit is scored. To keep your own visits out of your numbers, use a Signed-in email exclusion.
In the panel, both appear on the session detail dialog in a strip flagged with the accent, because they are the only facts there that Loghound did not derive for itself. The identity string is rendered as text at every sink like every other value that came off the wire — it is caller-supplied input on a public endpoint, and it is treated as hostile regardless of how trustworthy the site that sent it is.