Privacy Policy
Pennypoke is two surfaces sharing a brand: a Telegram bot that parses expense messages into your Google Sheet, and a splitter web app at pennypoke-app.pages.dev for shared expenses with friends. This page explains exactly what data touches our server on each surface, what we keep, and what we never see.
The short version. The bot is stateless: your receipts and messages exist only in memory for the few seconds we read them, and your extracted expenses live in your Google Sheet, not on our server. The splitter is account-based: it holds your email, a hashed session token, and the group/expense rows you and your group members create together. We never store IPs, user agents, geolocation, device fingerprints, analytics events, or an audit log. There is a self-serve JSON export of every byte we hold on you, and account deletion is reversible for 30 days then permanent.
01 What we process
When you send a text message to the bot or upload a receipt photo, our service:
- Parses the text (or sends the image to Anthropic's Claude Vision API) to extract amount, merchant, date, category, items.
- Sends the extracted data to your Google Sheet via the Google Sheets API.
- Frees the memory holding the message or image once the request completes.
We do not store your messages, receipts, or extracted data. The server is a Cloudflare Worker, a stateless compute environment with no disk, no filesystem, no persistent storage. Your input exists in memory for the few seconds it takes to process, then the process ends. This is architecturally enforced, not just a policy promise.
02 What we store on the Telegram bot side
For users of the Telegram bot, we store the minimum needed to operate the service:
- Account identifier. A hashed version of your Google account ID. We do not store your email or name on this side. We also store creation and last-use timestamps (for the 12-month inactivity auto-delete).
- Browser storage. A single authentication token kept in your browser's localStorage so you stay signed in on the bot's pairing page. This is strictly necessary for the service (ePrivacy Directive art. 5(3) exemption) and is not shared with anyone.
- Telegram chat ID. Stored if you use the Telegram bot, so we know which chat to reply to.
- Usage count. A number, how many entries you've written this month. Not the entries themselves.
- Subscription status. Whether you're on free, Pro, or Lifetime, plus (where applicable) an Apple StoreKit original transaction ID for users who paired the iOS app's lifetime unlock.
- Google Sheet ID. Which Sheet we should write to.
- Google OAuth token. Encrypted, stored in Cloudflare KV, auto-expires after 30 days of inactivity.
- Default currency. If you set one via
/currency, so you don't have to repeat it.
That's it. No expense data, no receipt images, no financial information.
02a What we store on the splitter web app side
The splitter is account-based, so it does store the rows you and the people you split with create together. Specifically:
- Account. Your email address (the only durable PII we hold), an email-verified timestamp from your first magic-link click, the account creation timestamp, and (only if you chose to set one) a password hash. Passwords are stored as PBKDF2-HMAC-SHA256 with 600,000 iterations and a per-user salt. We do not store your name; the display name you choose for a group lives only inside that group.
- Sessions. The SHA-256 hash of your session token, never the raw token. The cookie carries the raw token; lookup is by hash, so a database leak does not yield active sessions. The cookie is HttpOnly, Secure, and
SameSite=Nonein production (so the splitter web app atpennypoke-app.pages.devcan authenticate against the API atapi.pennypoke.com). - Magic-link tokens. SHA-256 hash of the one-time token, the email it was sent to, an expiry, and a "consumed" flag. Auto-purged after expiry.
- Groups, memberships, expenses, settlements. The shared-ledger rows you and your group members create. Expenses include amount, currency, description, payer, split details, optional notes, and timestamps.
- Group invitations. The invited email, a hashed token, role, expiry, and accepted-at timestamp.
- Telegram-bot link. If you bind your Telegram chat to your splitter account via the bot's
/linkcommand, we store a row mapping your Telegram chat ID to your splitter account ID, with the link timestamp. We also store short-lived (15 minutes) verification tokens (hashed) and the email being claimed during the magic-link confirmation step. The verification row is purged on use; the chat-ID-to-account-ID link persists until you delete your splitter account (cascade) or run a future/unlink. - Polar order metadata. If you pay, the email, plan, activation code, and order ID returned by Polar (see Sub-processors below).
- CSRF token. A second cookie used as the double-submit / Origin-allowlist anti-CSRF check on mutating endpoints. Strictly necessary.
What we explicitly do NOT store on the splitter: IP addresses, user agents, geolocation, device fingerprints, analytics events, or an audit log of any kind. There is no "last login" or "last seen" field anywhere in the splitter database.
03 Sub-processors
Your data passes through these services during processing:
| Service | Purpose | What they receive | Policy |
|---|---|---|---|
| Anthropic (Claude Vision) | Receipt photo OCR | Receipt image (transient) | Anthropic policy, API data not used for training |
| Google (Sheets API) | Writing rows to your Sheet | Extracted data | Google policy |
| Telegram | Bot messaging | Your messages to the bot | Telegram policy |
| Polar Software Inc. | Checkout, billing, VAT, invoicing, refunds (Merchant of Record) | Email, name, billing address, IP, card data (Polar is the legal seller) | Polar policy. US controller with EU SCCs and EU-US Data Privacy Framework certification. |
| Stripe (sub-processor of Polar) | Card processing, used internally by Polar | Card details. We never see them and have no contractual relationship with Stripe. | Stripe policy |
| Cloudflare | Hosting (Workers, Pages, D1, KV) | Request metadata, hashed account ID | Cloudflare policy. EU SCCs in place for data outside the EEA. |
| Brevo (Sendinblue) | Transactional email (splitter magic-link sign-in, group invitations). Polar receipts go through Polar's own email pipeline, not ours. | Recipient email address, message body | Brevo policy. EU-hosted (Paris), GDPR-compliant by default; no transatlantic transfer for our use case. |
International transfers to Polar (USA), Cloudflare, and Google rely on EU Standard Contractual Clauses plus the EU-US Data Privacy Framework where applicable. Where Polar acts as Merchant of Record they are a separate controller for buyer data; the operator listed below is a co-controller for the purchase metadata that flows back to Pennypoke (email, plan, order ID).
04 What we do not do
- We do not store receipt images, anywhere, ever.
- We do not log your expense messages or the extracted data.
- We do not sell, share, or use your data for advertising or ML training.
- We do not fingerprint browsers. Only strictly-necessary cookies are used: a session cookie on the splitter, a CSRF cookie on the splitter, and a sign-in token in localStorage on the bot pairing page. No analytics cookies. No third-party tag manager runs on the site.
- We do not run any analytics, ad tech, or product-analytics SDK on either surface. There is no Google Analytics, no Mixpanel, no Sentry session replay, no Hotjar.
- We do not email marketing unless you explicitly opt in.
04a Recipient categories
The splitter is operated by a single named person (see Data Controller below). That person, in their role as system administrator, can technically read account rows and group rows in the database when responding to support requests, abuse reports, or legal demands. There is an admin allowlist of email addresses that gates an internal admin panel; only operators on that allowlist can see other users' accounts. We log nothing automatically; the only access trail is the operator's own memory and the server logs that Cloudflare retains for 7 days for operational purposes.
Members of a group you create or join can see your display name (chosen by you for that group), the expenses you mark yourself as having paid or owing on, and the settlements you record. Group members cannot see your email address through the app UI.
05 Your rights
Under GDPR, CCPA, and similar laws you have the right to:
- Access and portability (Article 15 + 20). Splitter users have a self-serve JSON export at the bottom of the Account page. The export contains your account row, every group membership, every expense and settlement you appear in, every group invitation tied to your account or email, every Polar order linked to your email, your waitlist signup if any, and any legacy Telegram-bot row paired to this account by a paid order. For data still held only on the bot side (hashed Google ID, sheet pointer, OAuth token), email [email protected] and we will return a JSON dump within 30 days.
- Deletion (Article 17). Splitter: from the Account page, "Delete account" marks the account for permanent deletion, revokes all sessions immediately, and starts a 30-day grace period during which you can undo the deletion by signing back in. After the grace expires, the account row, sessions, magic-link tokens, group memberships, and any groups you owned are hard-deleted; the link from your account to your Polar payment records is severed (the order rows themselves are retained for 10 years to satisfy Lithuanian / EU tax-record requirements, but they no longer point at any living account). Bot side: email [email protected] to request the D1 row and KV OAuth token be removed; this happens within 48 hours.
- Rectification (Article 16). You can change the display name you use inside any group at any time. Email [email protected] to correct any other field.
- Objection and restriction. Revoke Google OAuth access for the bot any time via your Google account permissions page. Sign out of the splitter from the Account page to immediately invalidate your session. You may also ask us to stop processing your account while we investigate a complaint.
- Complaint to a supervisory authority. EU and UK users can lodge a complaint with their national data protection authority (list at edpb.europa.eu/members). The Lithuanian authority is the Valstybinė duomenų apsaugos inspekcija (VDAI).
To exercise any right, email [email protected].
05a Data controller
The data controller for Pennypoke is Povilas Konopackas, individual activity in the Republic of Lithuania, EU. Contact: [email protected]. This is also the address for any GDPR request, complaint, or right exercise. For purchases, Polar Software Inc. (3500 South DuPont Highway, Dover, DE 19901, USA) acts as Merchant of Record and is a separate controller for billing data; reach Polar at polar.sh/legal/privacy.
06 Data retention
- Messages and receipt images (bot). Not retained. Freed from memory after processing (seconds).
- Bot account data. Kept while your account is active. Auto-deleted after 12 months of inactivity.
- Splitter account data. Kept while your account exists. On user-initiated delete, marked for hard deletion after a 30-day grace; sessions are revoked immediately at the start of the grace.
- Splitter sessions. Cookie-bound; expire on sign-out, on account deletion, and after a fixed inactivity TTL on the server side.
- Magic-link tokens. Single-use; the row is marked consumed on first click and purged after expiry.
- Group invitations. Removed when accepted (or after expiry).
- Group / expense / settlement rows. Owned jointly by all members of a group. You can leave a group at any time, which removes your membership but does not erase past expenses created with you in the split, since those rows belong to the rest of the group as well; if you require complete erasure, deleting the entire group (which only the owner can do) is the path. Asking the operator at [email protected] is the fallback if you are not the owner.
- OAuth tokens (bot). Auto-expire after 30 days of inactivity.
- Order metadata. Email + plan + order ID retained while the order status is paid or activated. After hard-deletion of the user account, the link between the order row and the deleted account is severed; the order row itself is retained as long as Lithuanian / EU tax law requires (typically 10 years) for accounting purposes.
- Payment data. Held by Polar Software Inc. as Merchant of Record under Polar's policy. We do not hold any card data.
07 Security
All data in transit is encrypted via TLS. Data at rest in D1 and KV is encrypted by Cloudflare. OAuth tokens for the bot are additionally encrypted before storage. Splitter session and magic-link tokens are stored as SHA-256 hashes; passwords (when set) use PBKDF2-HMAC-SHA256 with 600,000 iterations. Mutating endpoints require a valid Origin / Referer header from the splitter web app's known origin (CSRF). We follow OWASP guidelines for the web application.
08 Children
This service is not directed at children under 16. We do not knowingly process data from children.
09 Changes
If we change this policy in a way that affects your rights, we will notify you via the email on your Google account or a banner on the site, at least 30 days before the change takes effect.
10 Contact
For privacy questions, email [email protected].