Identifying your users
Signing visitor identities with HMAC-SHA256 and your secret key, Laravel and Node.js examples, and email confirmation for unsigned visitors.
Reports are never anonymous. There are two ways a report is tied to a person:
- Signed: your server vouches for the visitor by signing their identity with your site's secret key. Their report is accepted immediately, and the email and name fields are hidden in the widget.
- Unsigned: the visitor types their email, and their report waits until they confirm that address by clicking a link in an email.
How signing works
- Your server computes
user_hash: the lowercase hex HMAC-SHA256 of the user'sexternal_id, using the site's Secret key (ur_sec_…) as the key. - Your page sets the identity before the widget script loads:
<script>
window.userreact = {
identity: {
external_id: "42",
email: "ada@example.com",
name: "Ada Lovelace",
user_hash: "…"
}
};
</script>
<script type="module" data-userreact data-key="ur_pub_…" src="…/widget/v1/ur.js"></script>
- Userreact recomputes the hash. If it matches, the reporter is marked verified for that site from then on.
Fields:
external_id: your own id for the user. It must be a JSON string: write"42", not42; a number is rejected.email,name: shown on the report.user_hash: the signature. Compute it on your server only. Never put the secret key in browser code.
Find your keys on the site's page, in the Keys card. The site page's Identify your users card has ready-made examples for Laravel and Node.js with a Copy example button, and the full Laravel guide (including Inertia and Livewire) is at Docs → Identify your users → Laravel.
Example: Node.js
import crypto from 'node:crypto';
const userHash = crypto
.createHmac('sha256', process.env.USERREACT_SECRET)
.update(String(user.id))
.digest('hex');
Example: Laravel (Blade partial)
{{-- resources/views/partials/userreact.blade.php, included before the widget script --}}
@auth
<script>
window.userreact = {
identity: @js([
'external_id' => (string) auth()->id(),
'email' => auth()->user()->email,
'name' => auth()->user()->name,
'user_hash' => hash_hmac('sha256', (string) auth()->id(), config('services.userreact.secret')),
]),
};
</script>
@endauth
Checking a signature by hand
printf '%s' '42' | openssl dgst -sha256 -hmac 'ur_sec_…'
The result must equal the user_hash your page sends for external_id "42". Common mistakes: hashing the email instead of the external_id, sending external_id as a number, using the public key instead of the secret key, or setting window.userreact after the widget script.
Without signing: email confirmation
When a visitor is not signed:
- They type their email in the widget and send the report.
- The report is stored but hidden: it is not in your inbox and does not count against your plan yet.
- They receive an email, "Confirm your email to send your feedback to {your domain}", explaining that the site does not accept anonymous reports.
- When they click the link, their waiting reports are released to your inbox and the page says "Your report is on its way". A visitor who confirmed earlier sees "Already confirmed".
The link is valid for 7 days. If it has expired, the page offers Email me a new link. Confirmation emails are sent at most every 15 minutes to the same person.
If your account is at its plan's report limit when the visitor confirms, their reports wait — nothing is lost — and are released when there is room.