Data Transparency

Neher Data Services LLC — Last updated February 2026

This page provides a plain-language technical explanation of exactly what data Calendar Wizard stores, how it is structured, where it lives, and how encryption works. There are no abstractions here — this is the actual implementation.

Who This Page Is For

This page is for technically curious users who want to understand exactly what is stored about them. For the legal policy, see the Privacy Policy. Both pages are authoritative; where they differ, the Privacy Policy governs.

Data Flow Overview

When you send a message to the Calendar Wizard bot, this is what happens:

You (Telegram app)
  |
  | Encrypted message via Telegram's servers
  v
Telegram Bot API (Telegram's infrastructure)
  |
  | Webhook POST to our server (HTTPS, TLS 1.2+)
  v
Calendar Wizard server (westoverxyz, private VPN)
  |
  |-- Parse command (held in memory only, never written to disk)
  |
  |-- Look up your account in PostgreSQL
  |   (telegram_id → credentials → google_oauth_token)
  |
  |-- Decrypt OAuth token in memory using pgcrypto
  |
  |-- Make API call to Google Calendar (HTTPS, TLS 1.2+)
  |   --> Google's servers
  |
  |-- Write result to PostgreSQL (event cache)
  |
  |-- Send reply back to you via Telegram Bot API
  v
You see the response in Telegram

At no point does your message text leave memory and get written to a database or log file. The only things that get written to the database are: your account row (on first connect), your encrypted OAuth token (on connect), your timezone (when you set it), and calendar event data (when events are created or fetched).

Database Schema

The Calendar Wizard service uses a PostgreSQL database. Below are the relevant tables and what each column contains. Column names are shown as they appear in the actual schema.

Table: calendar_users

One row per connected user account.

Column Type Content
id BIGINT PRIMARY KEY Internal numeric ID (auto-increment)
telegram_id BIGINT UNIQUE NOT NULL Your Telegram user ID (a number assigned by Telegram, e.g. 123456789). Not your username or phone number.
timezone TEXT IANA timezone string you set, e.g. America/New_York. Defaults to UTC.
created_at TIMESTAMPTZ When your account was created
last_seen_at TIMESTAMPTZ Timestamp of your last interaction with the bot

Table: calendar_credentials

One row per connected Google account. Linked to calendar_users.

Column Type Content
id BIGINT PRIMARY KEY Internal numeric ID
user_id BIGINT NOT NULL Foreign key to calendar_users.id
google_email TEXT Your Google account email address. Used only to identify which calendar was connected; not used for marketing or contact.
access_token_enc BYTEA Google OAuth access token, encrypted with pgcrypto symmetric encryption. The plaintext token is never stored.
refresh_token_enc BYTEA Google OAuth refresh token, encrypted with pgcrypto. Used to obtain new access tokens when the access token expires (every 1 hour).
token_expiry TIMESTAMPTZ When the current access token expires. Stored in plaintext because it contains no sensitive information.
connected_at TIMESTAMPTZ When you first connected Google Calendar
last_refreshed_at TIMESTAMPTZ When we last successfully refreshed the token

Table: calendar_events

Cached event records. This is a local copy of events from your Google Calendar. It is used to provide faster responses (without calling the Google API on every request) and to show reminders. It is not the primary store of your events. The authoritative source is always your Google Calendar.

Column Type Content
id BIGINT PRIMARY KEY Internal numeric ID
user_id BIGINT NOT NULL Foreign key to calendar_users.id
google_event_id TEXT Google's unique ID for the event (e.g. abc123xyz_20260301T140000Z)
summary TEXT Event title
start_time TIMESTAMPTZ Event start in UTC
end_time TIMESTAMPTZ Event end in UTC
description TEXT Event description, if present
location TEXT Event location, if present
synced_at TIMESTAMPTZ When this cache row was last updated from Google

Event cache rows older than 90 days since synced_at are automatically deleted by a nightly cleanup job.

Encryption Details

OAuth tokens are encrypted using PostgreSQL's built-in pgcrypto extension with symmetric (password-based) encryption. Here is the mechanism:

-- How tokens are encrypted before INSERT:
UPDATE calendar_credentials
SET access_token_enc = pgp_sym_encrypt(
    plaintext_token,
    current_setting('app.encryption_key')
)
WHERE user_id = $1;

-- How tokens are decrypted for use:
SELECT pgp_sym_decrypt(
    access_token_enc,
    current_setting('app.encryption_key')
)::TEXT AS access_token
FROM calendar_credentials
WHERE user_id = $1;

The encryption key (app.encryption_key) is a randomly generated 256-bit key that is:

This means that even if an attacker obtained a full database dump, they could not decrypt the OAuth tokens without also obtaining the encryption key from a separate location.

What Telegram Sees vs. What We See

Data Telegram sees We see We store
Your Telegram username Yes Yes (in webhook payload) No (not persisted)
Your Telegram user ID Yes Yes Yes (primary key)
Your phone number Yes No No
Message text Yes Yes (processed in memory) No
Message timestamps Yes Yes Indirectly via last_seen_at
Your IP address Yes (from Telegram app) No No

What Google Sees vs. What We See

Data Google sees We see We store
Your Google account email Yes Yes (from OAuth response) Yes (plaintext, in calendar_credentials)
Your calendar events Yes (it's their service) Yes (via API) Yes (cached, 90-day expiry)
OAuth scopes granted Yes Yes No (tracked by Google)
Your contacts Yes No (we do not request this scope) No
Your email content Yes (Gmail) No (we do not request this scope) No

Infrastructure

Understanding where the service runs helps you assess the risk profile:

Requesting Data Deletion

You have two ways to delete your data:

Option 1: Bot command

Send /delete to @nds_klaus_bot. The bot will ask for confirmation, then delete your row from calendar_users, calendar_credentials, and all rows in calendar_events associated with your account.

Option 2: Email request

Email james@westover.dev with the subject "Delete my data". Include your Telegram username or user ID if known so we can locate your record. We will confirm deletion within 48 hours and provide a confirmation number.

What happens when deletion runs

-- Deletion cascade (CASCADE is configured on foreign keys)
DELETE FROM calendar_users WHERE telegram_id = $1;
-- This automatically deletes:
--   calendar_credentials WHERE user_id = deleted_id
--   calendar_events WHERE user_id = deleted_id

Application logs that contain your Telegram user ID (used for debugging) are rotated and deleted after 30 days regardless of account status. There is no way to accelerate log deletion beyond that window, as logs are write-once for security auditability.

Note on Google Calendar

Deleting your account from our system does not delete events from your Google Calendar. We only manage events you explicitly ask us to create or modify. Your Google Calendar is your data; deleting our account removes our ability to access it, but does not affect the calendar content itself.

Westover Labs · Westover Labs · westover.dev