Privacy
Last updated: 29 October 2026
Who we are
Wattion is the energy service at wattion.app. The app is served at www.wattion.app. You can contact us at hello@wattion.app.
[TODO for Ben: legal entity name and registered address. Neither is in this repository.]
Data we hold
Signing in uses Clerk. Wattion stores your Clerk user id on a configuration row. It does not copy your Clerk profile into its own database. When you start Unlimited checkout, the server reads your name and email from Clerk and sends them to Stripe.
Device credentials are stored on the configurations table. Passwords, API tokens, OAuth tokens, and the Axle key are encrypted with AES-256-GCM before they are written. The encryption key is the ENCRYPTION_KEY environment variable. These columns are encrypted:
- ohme_password
- foxess_api_key
- foxess_oauth_access_token and foxess_oauth_refresh_token
- axle_vpp_api_key
- energy_provider_credentials (a JSON document)
- charger_provider_credentials (a JSON document)
The energy providers in the code, and the fields stored for each, are:
- FoxESS: an API token, or an OAuth access token and refresh token, plus the device serial number. The serial number is stored in plain text.
- GivEnergy inverter: an API token and inverter serial, inside the encrypted energy credentials.
- AlphaESS: an App ID, App Secret, and system serial, inside the encrypted energy credentials. The code uses these for monitoring.
- Ohme charger: the account email is stored in plain text in ohme_username, and again inside the encrypted charger credentials. The password is stored only in encrypted columns (ohme_password and those same charger credentials). Wattion sends that email and password to Google Identity Toolkit to sign in to Ohme.
- GivEnergy EV charger: an API token, an optional charger UUID, and an optional notification email, inside the encrypted charger credentials.
- Axle: an API key, encrypted, used to read a virtual-power-plant export window. The response is stored, including the raw payload.
If you pick "other" for an inverter or charger, the free-text interest you type is stored in plain text. Locale and timezone are stored in plain text. Solar site settings, including coordinates, panel tilt, panel direction, and tariff rates, are stored in plain text in solar_clip_settings.
The battery manager stores device and session readings in Postgres: charging sessions, battery and charger status, a change log, FoxESS power samples, solar-clip decision runs and day outcomes, and Axle export events. Those rows are tied to a configuration id, not to a separate copy of your name.
Unlimited billing stores a Stripe customer id, subscription id, status, and period end on the subscriptions table, next to your Clerk user id.
Why we use it
From the code, Wattion uses this data to:
- Sign you in and keep a configuration for your household.
- Call FoxESS, GivEnergy, AlphaESS, Ohme, and Axle so it can read status and, where that provider allows it, set a battery or charger schedule.
- Forecast solar generation and decide when to move battery energy that would otherwise be clipped.
- Record charging sessions, savings, and a history of schedule changes.
- Email a session summary and a monthly digest to the charger notification address, when one is stored.
- Take payment for the Unlimited plan through Stripe.
- Record product events in PostHog, sign-up events in Meta, and errors in Sentry, and post operational messages to Slack.
[TODO for Ben: the UK GDPR lawful basis for each of these uses (contract, legitimate interests, or consent). The repository does not record one.]
Solar forecast location
If you have not set a site location, Wattion may approximate one so it can forecast solar generation. It tries these sources in order:
- Location details your inverter provider already holds for the plant. For FoxESS, that is the plant address, city, postcode, country, or coordinates. For GivEnergy, that is the site latitude and longitude. If the GivEnergy site has no coordinates, Wattion uses the account address and postcode instead.
- A UK postcode you enter. Wattion looks that postcode up with postcodes.io. An address that is not a UK postcode is looked up with Nominatim (OpenStreetMap). If a provider postcode lookup does not return a point, and an address is available, that address is looked up with Nominatim as well.
- As a last resort, and only while you finish setup, your IP address, looked up with ipwho.is. Wattion keeps that estimate only when the country matches your timezone.
What each service receives
Wattion does not send your name, email address, or account details to these services.
- postcodes.io receives only the UK postcode, in the web address of the lookup.
- Nominatim (OpenStreetMap) receives the address text: street, city, postcode, and country, joined into one search. The request also names Wattion and includes the contact address hello@wattion.app. That is Wattion's contact address, not yours.
- ipwho.is receives only the public IP address of the connection.
The coordinates that come back are approximate. Wattion uses them only to forecast solar generation.
Nominatim results, including a result that an address was not found, are kept in memory for up to 30 days so the same address is not looked up again. postcodes.io and ipwho.is results are not kept in that cache.
Changing the location
In Settings you can replace an approximate location with a UK postcode, or replace any saved location with a precise one. Confirming an approximate location stores it as yours. A location you set or confirm yourself is never replaced automatically.
Services used for this lookup
- postcodes.io, for UK postcodes
- Nominatim / OpenStreetMap, for other addresses
- ipwho.is, for an IP address during setup
Other services that receive data
These are the services the code or environment examples actually call. This repository does not deploy the app on Vercel or Convex.
- Clerk, for accounts and the sign-in session. The browser is also allowed to contact clerk.com, clerk.wattion.app, accounts.wattion.app, and Clerk's development hosts named in the content-security policy.
- Railway, for the web app and the battery manager. Postgres is reached through DATABASE_URL. In a Railway environment the connection uses SSL. [TODO for Ben: which Railway region and which Postgres host are used in production.]
- Stripe, for Unlimited checkout, the customer record, the subscription, and the billing portal. Checkout sends your Clerk email, name, and user id.
- Mailgun, for session and monthly emails from noreply@wattion.app. The code uses api.eu.mailgun.net unless MAILGUN_REGION is set to us, in which case it uses api.mailgun.net.
- PostHog, for product analytics. Production falls back to the EU project. The browser does not start PostHog until you allow analytics. After that, events go through the first-party path /wtn-relay, which forwards to eu.i.posthog.com unless NEXT_PUBLIC_POSTHOG_HOST says otherwise. That proxy strips Cookie and Authorization. Signed-in browser events use the Clerk user id and do not send your email. The Clerk signup webhook and the Stripe webhook send server events even when analytics is off. Those events are described under Cookies, local storage, and analytics.
- Sentry, for errors. sendDefaultPii stays on, so the error SDK can attach the personal data Sentry includes by default. Error events are sent without waiting for cookie consent, and they are also posted to Wattion's own /api/notify-slack-error route. Session replay is added only after analytics consent. It then samples 10% of sessions and every session that hits an error, and it stops when analytics is turned off. [TODO for Ben: which Sentry region the DSN points at. It is not in the repository.]
- Slack incoming webhooks, for signup notices, setup choices, charging-session summaries, bug alerts, and error alerts. The signup notice sends the account created time and does not send your email. Setup notices send provider names and whether a credential is present, not the secret itself. [TODO for Ben: Slack workspace region.]
- Meta (Facebook) loads its browser pixel only after marketing consent. PageView and CompleteRegistration are described under Cookies, local storage, and analytics. When an email is present, the pixel hashes it in the browser for advanced matching.
- Meta Conversions API. If you allow marketing cookies, Wattion also sends sign-up and Unlimited checkout events to Meta from our servers, including a hashed copy of your email address, a hashed account id and, when present, Meta's click and browser identifiers (_fbc, _fbp), your browser user agent and, for checkout, your IP address. Checkout IP and user agent are kept in Postgres only until that event is sent, or for 24 hours if checkout is abandoned, and are not stored on the Stripe session. If you have not allowed marketing cookies, we do not send those events. If META_CAPI_CONSENT_MODE is set to always, the hashed email and hashed account id can still be sent when there is no consent record. An explicit reject is still not sent. The click ids, user agent, and IP address are still included only when marketing consent was granted.
- FoxESS Cloud, default host www.foxesscloud.com. GivEnergy Cloud, api.givenergy.cloud. AlphaESS OpenAPI, openapi.alphaess.com. Ohme, the host in OHME_API_BASE_URL (the example is api.ohme.io), after a Google Identity Toolkit password sign-in. Axle, api.axle.energy. [TODO for Ben: the country where each of these APIs stores data.]
- Open-Meteo, default host api.open-meteo.com. The forecast request sends latitude, longitude, panel tilt, and panel azimuth. It does not send your name or account id. A copy of the forecast is stored in open_meteo_forecast_snapshots.
- The National Grid ESO Carbon Intensity API at api.carbonintensity.org.uk. The request asks for the national Great Britain forecast. It does not send your account.
- postcodes.io, Nominatim / OpenStreetMap, and ipwho.is, as described above. [TODO for Ben: where each of those three hosts data.]
The content-security policy also allows cdn.jsdelivr.net, challenges.cloudflare.com, and maps.googleapis.com. This application code does not call those hosts itself. [TODO for Ben: whether Clerk, Stripe, or another script on the page loads them, and whether Wattion has its own Cloudflare or Google Maps account.]
Cookies, local storage, and analytics
Wattion shows a cookie banner before it uses optional analytics or marketing storage. Strictly necessary cookies stay on and cannot be switched off. Analytics and marketing stay off until you choose Accept all, or turn a category on in Cookie preferences. Reject all leaves both off. Cookie settings in the footer opens the same panel. A later change applies without a reload.
- Strictly necessary: the Clerk session cookie (__session). [TODO for Ben: the full list of cookies Clerk sets. It is not written out in this repository.] FoxESS OAuth sets two httpOnly cookies, foxess_oauth_state and foxess_oauth_return, for 10 minutes. They are removed when the connection finishes. The consent choice is stored in cc_cookie. Stripe.js is used for Checkout and the billing portal. [TODO for Ben: which cookies Stripe sets. They are not listed in this repository.]
- Analytics stays off until you opt in. PostHog is not started until analytics is allowed, so it does not write localStorage or call PostHog before then. After that, events go through the first-party path /wtn-relay to eu.i.posthog.com unless NEXT_PUBLIC_POSTHOG_HOST says otherwise. The proxy strips Cookie and Authorization. Signed-in events use the Clerk user id, not your email. Persistence is localStorage. Session replay may run after analytics consent if it is enabled in the PostHog project, and it stops when analytics is turned off. [TODO for Ben: whether session replay is enabled in the PostHog project settings.] Turning analytics off opts PostHog out and deletes its localStorage keys and any PostHog cookies.
- The Clerk user.created webhook and the Stripe webhook send PostHog events from the server even when the browser has not allowed analytics. The signup event is named "user signed up" and is identified by the Clerk user id. It does not include an email or the signup time. Stripe sends "checkout completed" with the currency, and "subscription cancelled" with a flag that the subscription ended, both identified by the Clerk user id. Neither includes an email.
- Sentry's error SDK still runs without analytics consent, and sendDefaultPii stays on. Error events also go to /api/notify-slack-error. Sentry session replay is added only after analytics consent and stops when analytics is turned off.
- Marketing stays off until you opt in. Meta's fbevents.js is not loaded until marketing consent, and there is no noscript PageView image. CompleteRegistration fires only after that consent and only for a new account. When an email is present, the pixel hashes it in the browser for advanced matching. After consent Wattion may set _fbc from an fbclid kept in memory from the landing URL, and Meta may set _fbp. Turning marketing off removes those cookies and stops the pixel. A reload before consent drops the click id unless it is still in the address bar. fbclid is not written to a cookie, localStorage, or sessionStorage before marketing consent. If you allow marketing cookies, Wattion also sends sign-up and Unlimited checkout events to Meta from our servers, as described under Other services that receive data. If you have not allowed marketing cookies, we do not send those events.
- utm_source, utm_medium, utm_campaign, utm_term, utm_content, and the landing referrer are kept in memory the same way, and sent to PostHog only after analytics consent.
How long we keep it
The code only enforces these limits:
- Nominatim lookup results stay in process memory for up to 30 days. They are gone when that process restarts.
- An Open-Meteo forecast stays in process memory for 60 minutes. The database copy is treated as fresh for 90 minutes, and may be reused for up to 24 hours if Open-Meteo refuses the next request. The code does not delete the old database row.
- The national carbon-intensity forecast stays in process memory for 60 minutes.
- The FoxESS OAuth cookies last 10 minutes.
- An Ohme sign-in token is kept in process memory and refreshed when it is older than 45 minutes. It is not written to the database.
Nothing in the code deletes old charging sessions, power samples, clip-decision rows, battery-manager logs, Axle events, configurations, or subscriptions on a timer. [TODO for Ben: how long each of those should be kept, and a deletion job if you want one.]
International transfers
PostHog traffic from this code goes to PostHog EU (eu.i.posthog.com and eu.posthog.com) unless NEXT_PUBLIC_POSTHOG_HOST points somewhere else, including the US ingest host. Mailgun traffic goes to the EU API unless MAILGUN_REGION is us.
[TODO for Ben: the hosting country for Clerk, Stripe, Sentry, Slack, Railway, Postgres, Meta, Google Identity Toolkit, FoxESS, GivEnergy, AlphaESS, Ohme, Axle, Open-Meteo, postcodes.io, Nominatim, and ipwho.is. The repository does not say.]
Your choices
You can replace a solar location in Settings, as described above. On the History page you can delete a configuration. That deletes the configurations row for your Clerk user id. The database schema has no foreign keys, so that delete does not remove charging sessions, power samples, clip decisions, status rows, change logs, or Axle events for that configuration.
The header shows Clerk's account button. The only Clerk webhook this app handles is user.created, which notifies Slack and PostHog. There is no user.deleted handler and no job that deletes Wattion rows when a Clerk user is removed.
The billing page can open Stripe's customer portal and can cancel Unlimited at the end of the period. That does not delete the configuration or the history.
To ask for a copy of your data, or for deletion, email hello@wattion.app. [TODO for Ben: a self-serve export, and a deletion flow that covers Clerk and every Wattion table. Neither exists in this repository.]
Children
The code does not ask for a date of birth and does not check age. [TODO for Ben: whether Wattion is only for adults, and any age limit you want stated here.]
Changes to this page
This page was published on 29 October 2026. The date at the top is the last day this text changed. [TODO for Ben: how you will tell existing users when this page changes. The app does not email them about policy updates.]