Home › Help › Privacy, data storage, and security: how ShootCal handles your data
Privacy, data storage, and security: how ShootCal handles your data
ShootCal is built so that your real schedule lives in your own Google account, not on ShootCal's server. When you use the web app, you sign in with Google, and ShootCal reads and writes your calendar, contacts, and tasks directly on Google's side. Your actual photos, client details, and event contents stay in Google and Apple's systems, which you already control.
ShootCal's own server (api.shootcal.com) runs a small database that mainly holds the things it needs to do its job: who you are (your Google account id, name, email, and profile picture), your app settings, your booking page and the requests clients send through it, your contracts, and a connection back to your Google account.
That connection is the one genuinely sensitive thing the server keeps, and it is encrypted. The keys that let ShootCal act on your Google calendar are scrambled before they are written to disk, using a secret key that is stored in a separate file outside the public website. If someone somehow copied the database, they would get a pile of unreadable scrambled tokens, not working access to anyone's Google account.
Every user's data is walled off from every other user's. ShootCal figures out who you are only from your private login cookie, never from anything the browser claims, so one photographer can never see another's calendar, clients, or contracts.
You stay in control. You can sign out anytime, and you can fully cut ShootCal off from your Google account from your own Google Account security settings, which immediately invalidates the stored connection. ShootCal never sells your data, never uses it for ads, and no human at ShootCal reads your Google data without your explicit say-so.
Where data actually lives: three places, not one
ShootCal deliberately spreads data across systems you already own so the central server holds as little as possible.
1. Your Google account. The web app reads and writes your real calendar events, contacts (inside a dedicated 'ShootCal Clients' group), and tasks (inside a dedicated 'ShootCal' list) directly through Google's APIs. Event titles, times, locations, attendees, and notes live in Google, not on ShootCal's server.
2. Apple iCloud (native apps). On iPhone, iPad, and Mac, your client list (names, phone numbers, addresses, notes) is stored in your private iCloud via CloudKit, and a subset of preferences syncs through iCloud Key-Value storage. The native apps store Google OAuth tokens in the device's hardware-encrypted System Keychain, not on ShootCal's server.
3. ShootCal's server: a single SQLite database file. The API at api.shootcal.com is PHP backed by one SQLite database. That file lives on ShootCal's server, outside the public web root, so it can never be requested directly through the website. This is where the web app's accounts, settings, booking data, and the encrypted Google connection live.
Exactly what the server's SQLite database stores
Everything the server keeps is scoped to your account and falls into these categories:
- Your account: who you are, taken from your Google sign-in (Google's stable account id, your email, name, and profile photo), plus timestamps and an approved flag. No password is ever stored, since your identity comes from Google.
- Login sessions: only a one-way hash of each session token, plus timestamps and a sliding expiry. The raw cookie value is never stored.
- Your Google connection: the encrypted Google tokens, the access-token expiry, and which permissions you granted. This is the sensitive part, and the tokens are encrypted at rest.
- Your settings: your preferences (home location for sunset timing, session types, and so on). These are non-sensitive, so they are not encrypted.
- Your booking pages and requests: your public availability feed configuration, your booking-page rules, the booking requests clients submit (name, email, phone, notes, chosen slot), and your imported booking history.
- Your contracts: your contracts, reusable packages, master template, and studio logo. Contracts intentionally store money as free-form text, and keep the signer's name, IP, and browser plus timestamps as the e-signature audit trail.
- Notifications and dedupe: browser push subscriptions and a guard that prevents duplicate reminders.
- Reviews and bug reports: marketing testimonials and in-app beta bug reports.
- Wave sync (optional): the state for the optional Wave accounting deposit-sync, whose stored tokens are encrypted the same way as your Google tokens.
- The native availability-feed accounts (Sign in with Apple) are kept entirely separate from the web accounts above.
Wherever an IP address is recorded, it is stored only as a salted, shortened one-way hash, never the raw IP.
How Google tokens are encrypted at rest
ShootCal uses libsodium's authenticated secretbox encryption (the well-regarded XSalsa20-Poly1305 construction) to scramble your Google tokens. The encryption key is a secret stored on the server, separate from the database.
Every value is encrypted with a fresh random nonce, so the same token never produces the same ciphertext twice. Because the encryption is authenticated, a tampered or corrupted value is rejected outright rather than decrypting to garbage.
Both your Google access token and refresh token are encrypted before they are written to the database. The design goal is plain: a database compromise alone yields no usable refresh tokens, because the encryption key lives in a separate, locked-down file outside the public web root, not in the database. If that key is missing, the web routes refuse to run at all, which is a deliberate fail-closed posture.
When tokens are actually used, the server decrypts the cached access token if it is still valid, otherwise decrypts the refresh token, asks Google to mint a fresh access token, and re-encrypts it before writing it back. Plaintext tokens exist only briefly in memory during a single request.
Per-user isolation: identity comes only from the server session
The isolation guarantee is simple: the browser holds only an opaque random token in an HttpOnly, Secure, SameSite=Lax cookie; the database stores only a one-way hash of it; and your identity is derived solely from the matching session record on the server. The browser never sends a user id or calendar owner that the server trusts.
In practice every data query is scoped to your resolved account, and edits and deletes additionally verify that the record actually belongs to you before touching it. Every piece of per-user data is tied back to your account so that it is automatically removed if the account is deleted, and so that one account can never reach another's records.
The OAuth flow and the Google scopes requested (and why each)
Sign-in is the standard Google web server flow with PKCE. A short-lived, encrypted, HttpOnly cookie protects the handshake; when Google sends you back, the returned state is checked with a constant-time comparison, the authorization code is exchanged, and Google's identity token is verified to come from Google and to be addressed to ShootCal before any account is created.
The permissions granted at sign-in, all classed by Google as 'sensitive' and none as 'restricted' (no full mailbox read):
- openid / email / profile: identify you and your Google account.
- calendar.events: create and edit your sessions.
- calendar.readonly: list your calendars and read events.
- contacts: the client manager, syncing only inside the ShootCal Clients group.
- gmail.send: send mail from the photographer's connected Gmail so no separate mail server is needed. This covers both the photographer's own alerts (new-booking-request and access-request notifications) and client-facing mail: booking confirmations and the relay of the photographer's typed reply to the client, plus test and confirmation sends. It is send-only and cannot read the mailbox; if declined, sends fail gracefully and the underlying record (for example a booking request) is still saved.
- drive.file: archive a signed-contract copy as a Google Doc in a ShootCal Contracts folder. drive.file only grants access to files the app itself creates, not your whole Drive.
Two permissions are requested only when you first use the feature that needs them, not at sign-in, so a normal sign-in stays on already-verified permissions and avoids the 'unverified app' warning:
- Tasks: the to-do and follow-up list, requested the first time you connect that feature.
- Other contacts (read-only): used only by the opt-in 'find client emails' scan to match a saved email to a scanned name, requested only when you turn that scan on.
When the native apps call ShootCal's server, the server verifies that the Google token was actually issued for ShootCal (and only accepts ShootCal's own apps), which defends against anyone trying to substitute a token from a different app.
What is never stored, never shared, and the public feed's privacy stripping
What the server never stores: your raw signup IP (only a one-way hash), your raw session cookie (only a hash), and, on the server, no plaintext Google tokens. The privacy policy adds that the developer runs no telemetry, SDKs, or trackers in the apps, and that Google data is never sold, never used for ads, and never human-reviewed without explicit consent. (The public marketing site does use Google Analytics for aggregate page traffic, which is entirely separate from the app and has no access to your in-app data.)
The public availability feed is deliberately content-free. It publishes only your BOOKED days, and each calendar entry carries only an id, timestamps, and a busy/confirmed status. Content-bearing fields like the title, description, location, and attendees are on a forbidden list, and a 'privacy audit' fails the build before anything is written to disk if any forbidden field is ever detected. So your public 'I'm booked that day' link cannot leak who the client is or what the shoot was.
Contract signer IPs are also minimized: a scheduled cleanup erases a signer's IP and browser details roughly three years after the session date (data-minimization for a third party who never made an account), while keeping the signer name and timestamp as the actual signature.
Revoking access and deleting data
Revoking Google access (privacy policy section 8): you can remove ShootCal from your Google Account security settings at any time. That immediately invalidates the stored refresh token, so the encrypted tokens on the server become useless and ShootCal can no longer act on your calendar.
Signing out deletes the server-side session record and clears the cookie. On native apps, Sign Out wipes the local cache and the device's stored login state.
Google-side data: deleting an event in the app moves it to your Google Calendar trash (recoverable 30 days); ShootCal does not retain its own copy of event contents.
Server-side account data: there is no self-serve 'delete my account' button in the web app today. Account and data deletion is handled by the operator, and the database is built for clean removal: deleting your account automatically cascades and removes all of your sessions, tokens, settings, feeds, bookings, contracts, packages, logo, and push subscriptions. The support contact for deletion requests is [email protected] (privacy policy section 10).
FAQ
If your database leaked, could an attacker read my Google calendar?
No, not from the database alone. The Google refresh and access tokens are stored encrypted using libsodium secretbox. Decryption requires a secret key that lives only in a separate, locked-down file outside the public web root, never in the database. A database-only compromise yields unreadable ciphertext. (An attacker who also obtained that key file would be a separate, deeper compromise.)
What encryption algorithm and parameters are used for tokens at rest?
libsodium's authenticated secretbox, which is XSalsa20 for the cipher and Poly1305 for authentication. Each value is encrypted with a fresh random nonce, and the nonce is stored alongside the ciphertext. The key is a secret held only on the server. Because it is authenticated encryption, a tampered value is rejected outright rather than decrypting to corrupted data.
Where exactly is my data stored, and is it one database or many?
The web app uses a single SQLite database file on ShootCal's server, outside the public web root. It is not Postgres or MySQL and not multiple databases. Your real calendar, contacts, and tasks content lives in Google; native client lists live in your private iCloud via CloudKit. The server database holds accounts, settings, booking data, contracts, and the encrypted Google connection.
How do you guarantee I can't see another photographer's data?
Your identity is resolved only on the server, from the one-way hash of your session cookie, never from anything the browser asserts. Every per-user query is scoped to your account, and edits and deletes additionally check that the record belongs to you. All of your data is tied back to your account so that there is no path for cross-account reads.
Why does ShootCal request gmail.send, and can it read my email?
The Gmail send permission lets ShootCal send mail from your connected Gmail so no separate mail server is needed. It sends both the photographer's own alerts (new-booking-request and access-request notices) and client-facing mail such as booking confirmations and the relay of your typed reply to a client, plus test and confirmation sends. It is send-only and grants no ability to read, modify, or delete mail. If you decline it, sends fail gracefully and the underlying record is still saved.
Why is the tasks permission handled differently from the others?
The Tasks and 'other contacts' permissions are still pending Google verification, so requesting them at sign-in would trigger the 'unverified app' warning on a first login. To avoid that, sign-in uses only the already-verified permissions, and these two are requested only when you first turn on the feature that needs them, which re-runs consent adding just the one extra permission. The app reports whether each was granted by checking which permissions you have already approved.
Does my public availability link expose client names or what I'm shooting?
No. The feed publishes only booked days, and each entry carries only an id, timestamps, and a busy/confirmed status. Content fields such as the title, description, location, and attendees are on a forbidden list, and a privacy audit fails the build before writing if any forbidden field is ever found. So the feed says 'busy that day' and nothing about who or what.
Do you store my IP address?
Not in raw form. For the light per-IP signup limit, only a salted, shortened one-way hash is stored, and the booking log stores a hash the same way. The one place a third-party IP is kept is a contract signer's IP and browser as e-signature corroboration, and a scheduled cleanup erases those roughly three years after the session date.
How do I fully revoke access and delete my data?
To cut off Google access immediately, remove ShootCal in your Google Account security settings; that invalidates the stored refresh token. Signing out deletes the server session record and clears the cookie. There is currently no self-serve account-deletion button in the web app; deletion is operator-handled via [email protected], and deleting your account cleanly cascades and drops all of your sessions, tokens, settings, bookings, and contracts.
Are session cookies and OAuth state protected against theft?
Yes. The session cookie is HttpOnly, Secure, and SameSite=Lax, and the database stores only a one-way hash of it, so a database leak yields no usable cookies. During sign-in, the handshake state is kept in a short-lived (10-minute), encrypted, HttpOnly cookie, the returned state is compared with a constant-time check, and Google's identity token is validated to come from Google and to be addressed to ShootCal before any account is created.
Does any of my Google data go to Anthropic or other third parties?
Only if you opt into the AI Client Scan or web Quick Add. Per the privacy policy section 4, in that case only event titles and the date range are sent through api.shootcal.com to Anthropic to suggest client names, never attendees, notes, or other details, and it is not used to train models. The native Quick Add parser runs fully on-device. Nothing about your Google data is sold or used for advertising.