Harvv is built on a simple principle: we should be able to tell you exactly what's broken on your site without knowing anything about your users.
Consent-Aware by Design
When consent-aware mode is enabled for a site, the pixel detects the site's consent mechanism (Shopify's Customer Privacy API, or any banner wired to Google Consent Mode) and obeys it on its own:
- A visitor declines: nothing is transmitted. Not a reduced ping; zero bytes. Nothing is written to their device.
- A visitor is deciding: events wait in the browser's memory only. If the answer is no, or they leave, the events are discarded.
- A visitor accepts: the session transmits normally, and identifiers are written only after consent resolves.
- No banner present: the site owner's configuration governs.
You can verify this yourself: open DevTools on a consent-gated site, decline the banner, and watch the network tab. There is nothing to see, which is the point.
An Open Pixel. Read Every Line.
The pixel is about 27 KB gzipped of dependency-free vanilla JavaScript, and the full unminified source is published at /pixel-source. Most session-replay and analytics scripts are many times that size and effectively unauditable. Ours is an afternoon read, and we mean for you to read it.
What We Collect
| Signal | What we capture | What we DON'T capture |
|---|---|---|
| Clicks and taps | Element identity (tag, id, class) and a short visible label for interactive controls like buttons and links | Not input-field text. Not form values. Not content text. |
| Scroll and reading | Depth reached, scroll velocity, and skim-vs-read distance (kinematics only) | Not what content was on screen |
| Text selection | Which element, how many characters | Not the selected text itself |
| Keyboard | Tab and Escape key identity only (navigation and dismissal) | No letters, numbers, passwords, or any typed content |
| Identity | Random 16-character hex ID, first-party only, written only when consent allows | No name, email, or raw IP address |
| What the site tells us | Only what the site's own code passes to window.harvv.user: short labels a merchant chooses, such as a customer tier or a B2B flag. At most 12 labels, 40 characters each. Nothing is read from the platform's customer record by us. | We never read this from your account, your order history, or any logged-in profile. Nested objects are discarded rather than flattened, so a customer record cannot be passed through it by accident. Whatever a site chooses to put here is the site's decision and is covered by that site's own privacy policy. |
| Device and browser | Device class (mobile, desktop, tablet), browser brand, operating system and its version, and on mobile the device model, read from the browser's UA Client Hints | Not the raw user-agent string. These are recorded as attributes of one session and are never combined into an identifier, never used to recognise the same person across sessions, and never used across sites. |
| Performance | Core Web Vitals (LCP, INP, CLS, TTFB), the LCP element's type and structural selector, and the asset path of an image LCP | Never the element's text content; asset query strings are stripped |
| Errors and network | JavaScript error messages, failing request status codes and paths | No request bodies or payloads; sensitive query keys scrubbed |
| Page audits | On public pages: meta-tag lengths, heading counts, structured-data types, word counts | Skipped entirely on login, account, checkout, and admin URLs and anything marked data-harvv-private |
A Note on IP Addresses
The behavioral pixel never sends your visitors' IP addresses. It is not in the payload, and there is no IP column in the database table where behavioral events live, so an IP can never be joined to what a visitor did on your site. That is the claim above, and it holds.
For completeness, because auditors ask: like every web server on the internet, ours does see the connecting IP on each request. That is how a network connection works. We use it transiently, in memory, for one thing only: rate limiting and abuse prevention. We do not store the raw IP. For security investigations our internal access log keeps only a keyed, daily-rotating hash of it (HMAC, not a plain hash) that cannot be reversed back to the address and cannot be linked across days, and it is never joined to any behavioral event. In short: no IP in your analytics, ever, and no raw IP retained anywhere.
What We Cannot Do
- We cannot read form inputs. If a user types their credit card number, we see the element identifier, never the value.
- We cannot see page content. We don't capture DOM snapshots, screenshots, or session recordings. Session replay is where analytics goes wrong; we simply don't have the capability.
- We cannot track users across sites. Our identifiers are first-party, scoped to your domain only.
- We cannot identify individual users. Our visitor ID is a random hex string with no connection to real identity.
- We cannot override a visitor's no. Under consent-aware mode, a declined banner means the pixel transmits nothing, and there is no server-side fallback that collects anyway.
Architecture
- Pixel: about 27 KB gzipped. Vanilla JavaScript, dependency-free, loads async so it never blocks your page.
- Data transfer: events batch in the browser and ship every 10 seconds over HTTPS (with
sendBeaconas the page closes). JSON with short keys (v, s, e, t, d). - Storage: PostgreSQL on Railway (US region). Raw event rows live approximately 30 days, then move to a compressed archive kept 13 months by default and never more than 24 months, on a schedule our own monitoring verifies against the bucket. Aggregates and reports live for the life of your account. Conversion outcomes are kept durably. Deleting a site deletes the archive too, within 90 days.
- IPs at rest: none in analytics records, anywhere. See the note above.
- AI analysis: Anthropic Claude API for issue diagnosis. Anthropic's policy prohibits training on API inputs.
- Email: Resend for transactional email. Resend's privacy policy.
- Payments: Stripe for billing. We never see or store card numbers.
Subprocessors
| Service | Purpose | Data Shared |
|---|---|---|
| Railway | Hosting and database | All event data (stored) |
| AWS S3 | Encrypted backups and the raw-event archive | Encrypted database backups and archived event rows |
| Cloudflare | CDN, WAF, edge queue | Requests in transit; nothing stored |
| Anthropic | AI analysis | Aggregated behavioral patterns (no PII) |
| Resend | Transactional email | Recipient email addresses only |
| Stripe | Payment processing | Billing info (handled by Stripe) |
| Google LLC | Optional GA4 / Search Console integrations, read-only, at your instruction | Nothing flows to Google; we read aggregated reports you authorize |
| Apollo.io | Customer profile enrichment (account holders, optional) | Signup email for company lookup |
| Slack | Optional Slack integration, at your instruction | Finding titles, site names and page paths you choose to post; questions you type to the Harvv bot; an encrypted bot token |
How We Approach Privacy Law
- Our role is contractual, not incidental. Our Terms bind Harvv as your service provider and processor: no selling or sharing visitor data, no use beyond providing the service, no combining across customers except deidentified aggregates.
- Consent is honored mechanically. Where your site collects consent through a mechanism we recognize, the pixel obeys it automatically, as described above. Where your visitors' law requires consent, deploying the banner is your call and your responsibility; honoring it is ours.
- We do not sell personal information, and we do not share data with third parties for their own purposes.
- We make design claims, not legal guarantees. The pixel is built to avoid collecting personal data; whether your specific deployment needs a banner depends on your jurisdictions and what else your site runs. Our Privacy Policy spells out every field we touch.
Verify It Yourself
- Read the full pixel source code, every line.
- Open DevTools, Network tab, filter by "harvv" or "pixel.js", and inspect every request we make.
- Check the payload: JSON with short keys (v=visitor, s=session, e=event type, t=timestamp, d=data). No PII anywhere.
- On a consent-gated site with consent-aware mode on, decline the banner and watch: zero requests.
Questions? Email jordan@harvv.com and I'll personally walk you through the code.