Security & data boundaries
This page says what it reads, what it stores, how long it keeps it, and how secrets are protected. It describes the code as it runs today, and claims nothing more.
We have no SOC 2 report and no ISO 27001 certificate. If your security review needs answers, email us and you will get them from the person who wrote the code.
Where your data lives
- Servers and the database run at Hetzner in Germany.
- Cloudflare sits in front of meterbird.com and app.meterbird.com.
- The other companies that handle data for us are listed in the DPA.
What each connection reads
Meterbird only reads from the services you connect. Each connection screen lists the permissions it needs. Where a provider offers read-only access, that is all we ask for:
- Stripe: a restricted key with Read on charges, refunds, disputes, Checkout sessions, payment intents, customers, subscriptions, invoices and balance transactions. Secret keys are refused.
- Paddle:
transaction.read,subscription.read,adjustment.read. Polar:orders:read,subscriptions:read. - Shopify:
read_orders, and optionallyread_all_ordersandread_own_subscription_contracts. Customer names, addresses and emails are never requested. - Google Search Console:
webmasters.readonly. Google Analytics 4 import:analytics.readonly, with a key used once and not stored. - Meta Ads:
ads_read, for campaign spend, impressions, clicks and reported purchase value. - Sentry: Read on Organization, Project and Issue & Event. Issue titles, levels and counts only, never event payloads or stack traces.
- Cloudflare analytics: Zone Read and Analytics Read. Cloudflare Pages: Pages Read; a token that can write is refused.
- GitHub: a GitHub App with Contents and Metadata, both read-only, on the repositories you pick. It reads commit messages and authors, and the names (never the values) of dependencies and environment variables in a few root files.
- Vercel (projects and deployments) and Supabase (projects and their health): read access, as shown on each provider's consent screen.
- UptimeRobot and Better Stack imports: a read-only key, used once and not stored.
Except for Cloudflare Pages, Meterbird does not test whether a key you paste could also write. It asks for a read-only one and only ever reads with it.
Three services offer no read-only access. Their credentials could make changes, and the connection screen says so:
- Google Ads: Google's only Ads scope can also write. Meterbird sends only reporting queries.
- Lemon Squeezy: API keys cannot be limited to reading.
- Railway: a project token can change that environment.
Some connections hold no credential at all. Netlify, Clerk, Postmark and Auth0 send events to Meterbird, and each one is checked against a secret or signature before anything is stored.
The only changes Meterbird makes in a connected service: it subscribes its own endpoint to Shopify order and subscription webhooks, and when you disconnect Vercel, Meta or Supabase it revokes its own access.
How credentials are stored
- API keys and tokens you give us for a connection, alert channel URLs and signing secrets, and credentials a monitor sends with its request are encrypted with AES-256-GCM before they reach the database.
- Each one is bound to the row it belongs to, so a ciphertext copied to another connection will not decrypt.
- The encryption keys live in the server's environment, not in the database. Without a valid key nothing is stored and nothing falls back to plain text. Keys can be rotated, and every stored secret is re-encrypted under the new one.
- A decrypted credential is only used by the background worker. It is never sent back to a page or an API response, and prints as “[redacted]” in logs.
- Meterbird API keys are stored as SHA-256 hashes; only a short hint is kept to tell them apart.
- Sign-in links are stored hashed. GitHub sign-in tokens are stored encrypted.
- The web app and worker connect to Postgres as a role that can read and write rows but cannot change the schema.
Separation between workspaces
Every read and write of workspace data is checked against your membership and role. Public status pages and share links show only what you chose to publish. This is enforced in application code and covered by a tenant-isolation test suite that runs in CI on every change. The database does not use row-level security.
What the tracker stores
- No cookies. The tracker never reads or writes a cookie.
- Default mode keeps a random visitor ID in
localStorageand a random session ID insessionStorage. It also queues unsent events inlocalStorageuntil the server has answered. When the browser sends Do Not Track, that visit is counted the cookieless way instead: nothing is stored on the device, the server replaces the IDs with a daily hash, and the visit is never linked to a user, recorded on replay or tied to an IP address. - Cookieless mode stores nothing on the device. The server derives a visitor ID from a hash of a random daily salt, the site, the IP address and the browser's user agent. Each day's salt is deleted the next day, so a visitor cannot be followed from one day to the next.
- IP addresses are used to look up the country and to screen out bots and hosting networks. They are not stored with analytics data, and the collector does not log them. The one exception is off by default: a site owner can turn on recording of the IP addresses of customers their site has identified, as evidence for payment disputes. Those addresses are kept 13 months after they were last seen, deleted when the user is erased, and never kept for anonymous visitors. A site that turns this on must say so in its own privacy policy.
- What is stored per event: the page path without its query string, the referring site's address without its query string, UTM tags, browser, operating system, device type, country, and any event name and properties the site sends.
identify()stores the user ID and traits the site chooses to pass. Sites that never call it send no user identity.- Session recording is off by default and turned on per environment. It records a sample of visitors (5% by default), never in cookieless mode, and stops after 10 minutes. All text and form values are masked in the browser before anything is sent, images become grey boxes, and network traffic and the console are never recorded.
How long data is kept
- Free: 30 days. Founder: 1 year. Studio: 2 years. A trial keeps Founder's window while it runs.
- Every night, events, sessions, frontend errors and Web Vitals, crawler and server records, monitor checks, imported history and
identify()links older than the plan's window are deleted. - Session recordings are deleted after 14 days on every plan.
- Events stored past the monthly allowance are deleted after 7 days unless the plan grows to cover them.
- When a workspace moves to a smaller plan, data older than the new window goes at the next nightly trim.
- Deleting a workspace deletes its data from the database at once. You can also erase one tracked person by the user ID your site passed to
identify(), and export your data at any time.
AI features
AI summaries and Ask are off by default. Only a workspace owner on a paid plan can turn them on. Then Anthropic's Claude receives incident fact texts, the weekly digest's figures and names, and the figures Ask reads to answer a question, as the member asking. It never receives member emails or individual visitors. Incident summaries are stored with their incident; nothing else is stored.
Reporting a security issue
Email [email protected] with “Security” in the subject. Include what you found and how to reproduce it. We will reply within three working days and keep you updated until it is fixed. Please do not access other customers' data, run denial-of-service tests or publish the issue before it is fixed. We do not run a paid bug bounty, but we will credit you if you want.