Appearance
Privacy, data storage, and security: how ShootCal handles your data
- If your database leaked, could an attacker read my Google calendar?
- What encryption algorithm and parameters are used for tokens at rest?
- Where exactly is my data stored, and is it one database or many?
- How does ShootCal separate each photographer's data?
- Why does ShootCal request gmail.send, and can it read my email?
- How do I review ShootCal's Google permissions?
- Does my public availability link expose client names or what I'm shooting?
- Do you store my IP address?
- How do I fully revoke access and delete my data?
- Are session cookies and OAuth state protected against theft?
- What data goes to OpenAI for AI features?
- What data goes to OpenAI for AI setup?
- What happens to messages in the shootcal.com chat?
ShootCal stores your real schedule and business records in your own ShootCal account: your appointments, clients, tasks and follow-up plans, session types, booking pages and requests, contracts, invoices, payment history, and reminders all live on ShootCal's server, scoped to you. Sign-in identifies your ShootCal account; connecting Google or Microsoft is a separate optional choice that adds calendar sync. Galleries photos live in ShootCal Galleries storage.
ShootCal's own server (api.shootcal.com) runs a small database that holds the things it needs to do its job: your verified account identity, a one-way hash of your password when one is set, your appointments with their schedule and business fields, your saved clients and tasks, your app settings, your booking page and requests, your contracts, your optional invoices and payment records, and, only if you connect one, your optional Google or Microsoft account link.
Your appointments, client details, and paperwork can contain sensitive information. A connected provider also gives ShootCal access credentials, which are encrypted. The keys that let ShootCal act on your Google or Microsoft 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 provider account.
ShootCal resolves your identity from a server-issued session, using the browser's private login cookie or an explicit native bearer credential, rather than trusting a client-supplied account id. Account-scoped queries and record-ownership checks help keep each photographer's calendar, clients, and paperwork separate.
You stay in control. You can sign out anytime, disconnect a calendar provider in Settings, and 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 provider data without your explicit say-so.
Where data actually lives: three places, not one
ShootCal keeps your account data on its own server, keeps native-app preferences on your Apple devices, and, only when you connect one, places schedule copies with one calendar provider.
- ShootCal's server: a dedicated MariaDB database. The API at api.shootcal.com is PHP backed by a MariaDB database server running on the same machine. The database is not something the website could ever serve up: the database server accepts connections only from the server itself, never over the internet, and only with private credentials held by the ShootCal application. This is where your account, appointments, clients, tasks, follow-up plans, settings, booking data, contracts, invoices, and the encrypted provider connection live.
- Apple iCloud and the device (native apps). On iPhone, iPad, and Mac, some device preferences may use iCloud storage; your shared session types are saved in ShootCal and edited on the web, and native ShootCal sign-in credentials are kept in the Apple System Keychain. The server stores only a one-way hash of the ShootCal session token. Native apps can also keep local snapshots for browsing previously loaded data; those snapshots do not replace the records saved in ShootCal. Calendar-sync credentials are managed separately on ShootCal's server. Use the native Mac app from the App Store or the browser-installed web app described in the device guide. Their offline capabilities differ; follow the behavior of the version you have installed.
- Your connected calendar provider (optional). If you assign an appointment to a chosen connected Google or Microsoft calendar, a copy of its ordinary schedule fields (title, description, start/end, all-day, location, and status) lives there. Appointments can remain in ShootCal only; selected connected calendars can also supply busy-time checks. Separately enabled Google integrations may import older contacts or a selected Google task list, or archive paperwork to Drive. Imports leave Google unchanged; later edits to the copied clients and tasks are saved in ShootCal. These features are separate from calendar consent. Business fields such as client records, tasks, internal notes, contracts, and payments are excluded from calendar sync. Explicitly chosen Drive paperwork archives are separate and can contain the contract, invoice, and payment details you choose to archive.
Exactly what the server's database stores
Everything the server keeps is scoped to your account and falls into these categories:
- Your account: your verified email, name, optional profile photo, timestamps and approval state, an optional linked Google account id, and a one-way hash of your ShootCal password if one is set. The original password cannot be recovered from that hash and is never copied into Galleries.
- Login sessions: only a one-way hash of each ShootCal session token, plus timestamps and expiry. Browsers send the token in a protected cookie; native account sessions use an explicit bearer credential. The raw session token is not stored in the database.
- Your provider connection (only if you connect one): the encrypted Google or Microsoft tokens, the access-token expiry, and which permissions you granted. This is the sensitive part, and the tokens are encrypted at rest.
- Your settings: preferences such as home location for sunset timing, session types, and reusable follow-up templates. These records are stored in the account database and are not encrypted with the provider-token key.
- Your appointments: every session's schedule (title, start and end, all-day flag and timezone, location, and status) plus its business metadata (session type, client name and email, your internal notes, and the paid/unpaid deposit marker). This is the primary copy of your schedule; a connected provider calendar holds only a synced copy of the schedule fields.
- Your clients and tasks: the saved roster, contact details and internal client notes, tasks with titles, due dates, notes and completion state, their client or appointment relationships, and the selected appointment follow-up plans. Explicit imports retain source identity and original task archives for review and recovery.
- 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.
- Your invoices (optional): the one-time setup acknowledgement and invoice settings; client name and optional email; invoice number, status, dates, USD line items, tax, memo, and the manual payment instructions frozen when issued; an optional link to the exact ShootCal appointment, plus retained legacy calendar-link identifiers where present; client payment reports; Stripe account, checkout, payment, refund, and reconciliation identifiers and statuses; and confirmed payments, refunds, and reversals. Invoice amounts are stored as integer cents. Public invoice access secrets are validated from a one-way hash, with the retrievable copy encrypted at rest. For payment-report abuse prevention, the claim stores a full SHA-256 hash derived from a server secret and the connection IP, never the raw IP. Stripe, not ShootCal, collects the client’s card details; ShootCal does not store full card numbers or bank credentials. Instructions, references, and notes are free text, so never put credentials or other secrets in them.
- Notifications and delivery records: browser push subscriptions, reminder deduplication, and mail delivery or recovery records used to track the selected sender and delivery result. An uncertain send is not proof of delivery and must not silently become a new send.
- Reviews and bug reports: marketing testimonials and in-app bug reports.
- The native availability-feed accounts (Sign in with Apple) are kept entirely separate from the web accounts above.
Abuse-control IP handling depends on the record. Signup and booking controls store only shortened one-way hashes. An invoice payment claim stores a full keyed SHA-256 hash so repeat submissions to the same invoice can be limited; its raw IP is not retained. The documented exception is a contract e-signature: the signer's raw IP and browser are kept temporarily as signature corroboration and cleaned up roughly three years after the session date.
How provider tokens are encrypted at rest
ShootCal uses libsodium's authenticated secretbox encryption (the well-regarded XSalsa20-Poly1305 construction) to scramble your Google or Microsoft 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 provider 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 the provider 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 browser holds an opaque random token in an HttpOnly, Secure, SameSite=Lax cookie. Native account sessions send an explicit ShootCal bearer credential. The database stores only a one-way hash of the session token, and identity comes from the matching server session and account record. A client-supplied user id or calendar owner is not authority to read another account's records.
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. Account ownership and guarded deletion checks keep account records separate. Account deletion requires the review and retention handling described below; a recorded request or revoked sign-in does not mean that every record has been removed.
The OAuth flow and the Google scopes requested (and why each)
This flow runs when you choose to connect Google; Microsoft connection uses Microsoft's equivalent consent flow. 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.
Identity sign-in and calendar consent are separate. Calendar consent supports schedule sync and busy-time checks. Older or separately enabled Google integrations may use additional permissions; review the consent screen for the action you choose. The following describes those permissions, rather than a required sign-in bundle:
- 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: an explicit import of older Google clients, separate from the saved ShootCal roster.
- gmail.send: existing accounts may use Gmail sending. ShootCal also has its own email delivery, without requiring Google. This covers both the photographer's own alerts (new-booking-request and access-request notifications) and client-facing mail: booking confirmations, contract and invoice notices, 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 or issued invoice) is still saved.
- drive.file: archive a signed-contract PDF copy in a ShootCal Contracts folder. drive.file only grants access to files the app itself creates, not your whole Drive.
Some optional permissions are requested only when you first use the feature that needs them, not at sign-in, so users who never enable a feature never grant its permission:
- Tasks: optional discovery and one-time import of a Google task list you explicitly select. Your ordinary ShootCal tasks and follow-ups do not require this permission.
- 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.
Native account API requests use a ShootCal-issued session credential. A separate Google credential exchange, where used, validates Google's token audience and subject before issuing that session for an already-linked ShootCal account. It does not infer ownership from a matching email address or grant calendar consent.
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), the raw IP behind an invoice payment claim (only the full keyed SHA-256 hash described above), your raw session cookie (only a hash), or plaintext Google tokens. Stripe collects card details on its hosted payment page; ShootCal stores the payment and refund identifiers, states, amounts, and ledger results needed for the invoice, but not full card numbers or bank credentials. Because manual-payment instructions, references, and notes are free text, you must not enter credentials or other secrets in them. Invoice access uses a high-entropy secret: its validation value is one-way hashed and the copy needed for the photographer to copy the link again is encrypted, while the client sends the secret from the URL fragment so it does not enter ordinary request or access logs. The separate raw contract-signer IP exception is described above. The privacy policy adds that the developer runs no general 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 separately uses Google Analytics and Google Ads for aggregate traffic, with an explicit choice offered wherever consent is required before either loads; a signup event is emitted only from a server-attested genuinely new Galleries studio and carries no calendar, contact, client, or gallery content.
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 ends the active ShootCal session and clears the browser cookie or active native sign-in state. Local recovery archives or cloud preferences can remain separately to protect interrupted work. Signing out or uninstalling an app is not a request to delete your ShootCal account.
Appointments: canceling an appointment in ShootCal keeps a canceled record so your history stays intact. Cancellation of a linked calendar copy is a separate sync result and can remain pending or need review; a saved cancellation is not confirmation that the provider accepted it. Schedule copies in a connected Google or Microsoft calendar can also be edited or deleted in that account at any time.
Server-side account data: there is no instant self-serve deletion. When you are signed in to your ShootCal account, you can open the account deletion request page and confirm a request. The request is recorded for operator review; it does not immediately delete data, revoke provider access, or cancel billing. We review requests on business days, acknowledge them within two business days, and complete eligible deletions within 30 days of the request. When an operator processes deletion, account access closes and protected account data and files remain restorable for 30 days. Permanent cleanup follows after this recovery window, subject to required record retention and any unresolved cleanup safeguards.
We retain financial records only when required. Deleting an account closes access and starts the 30-day recovery window while preserving its records. Permanent cleanup is held when financial records or payment connections remain, such as contracts, invoices, payments, refunds, expenses, Stripe, or accounting connections. Those accounts require a separate reviewed retention and deletion plan that identifies required records and the other content to remove; the automatic cleanup does not bypass these requirements. The operator explains what must be retained, why, and any remaining steps or blockers. A blocked request remains pending until its actual processing and cleanup are verified; revoked access or queued cleanup is not completed deletion.
Within an active account, drafts may be deleted; invoice voids and payment reversals preserve the original financial history, and only a clean void invoice with no payment, refund, client-report, Stripe, booking, or uncertain-delivery history can be permanently deleted. The support contact for deletion requests is support@shootcal.com (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 dedicated MariaDB database on ShootCal's server. The database server accepts only local connections from the machine it runs on, never from the internet, and requires private application credentials. ShootCal Galleries is a separate product and keeps its data in its own separate database with its own credentials, so the two products' data stay apart even at the database level. Your appointments and business records live in ShootCal's database. Your saved client roster and tasks live in ShootCal even without a provider. You may explicitly import older Google contacts or a selected task list; that does not establish continuous contact or task sync. An appointment can remain in ShootCal only or have a schedule copy in a chosen connected Google or Microsoft calendar. The server database holds accounts, appointments, clients, tasks, settings, booking data, contracts, optional invoices and payment history, and the encrypted provider connection.
How does ShootCal separate each photographer's data?
Your identity is resolved on the server from the one-way hash of your ShootCal session token, sent in a browser cookie or an explicit native bearer credential, rather than from a client-supplied account id. Account APIs scope queries to the resolved owner, and edits and deletes also check record ownership. These safeguards are designed to prevent cross-account access.
Why does ShootCal request gmail.send, and can it read my email?
Existing accounts may use Gmail sending through Google's gmail.send permission. It allows sending, not reading, modifying, or deleting existing mail. ShootCal also has its own transactional email delivery through SMTP2GO, with the studio's verified address as Reply-To, so Gmail is not required for a standalone account. That service receives the recipients, message content, and any attachments needed for delivery. The selected sender is preserved with a send attempt; an expired credential or uncertain delivery does not silently switch it to another sender.
How do I review ShootCal's Google permissions?
Review the provider consent screen for the particular feature you choose, and review existing access in your Google Account settings. Identity sign-in, calendar access, and optional import or sending permissions serve different purposes. ShootCal checks granted permissions before using the corresponding feature; a connection alone does not prove that every optional permission was granted.
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?
For signup and booking abuse controls, only shortened one-way hashes are stored. An invoice payment claim keeps a full SHA-256 hash derived from a server secret and the connection IP; it is one-way and the raw IP is not stored. The separate exception is a contract signer's raw IP and browser, retained as e-signature corroboration and erased by a scheduled cleanup 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. You can request account deletion by signing in to your existing ShootCal account and confirming the request. The request is recorded for manual review; it does not immediately erase data, revoke provider access, or cancel a subscription. We review requests on business days, acknowledge them within two business days, and complete eligible deletions within 30 days of the request. Export any records or photos you need and review subscriptions with the service that bills you. We retain financial records only when required. Financial records or payment connections require a separate review and deletion plan; the operator explains what must be retained, why, and any remaining steps or blockers. A pending request is not confirmation that deletion is complete. Contact support@shootcal.com for help with an existing request or if you cannot sign in.
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.
What data goes to OpenAI for AI features?
These optional features use OpenAI through ShootCal’s backend, with different inputs:
- Client Scan: bounded event titles to suggest names for your review. Date-range metadata, configured session types, attendees, and private notes stay out of the model request. Titles can still contain personal information you entered.
- Web Quick Add: your entered line, current time, time zone, and configured session-type names to fill an event draft. Review it before saving; parsing does not create an appointment.
- ShootCal Assistant: your question and workspace context with relevant first-party help excerpts. It retains privacy filtering and does not load your calendar, roster, or paperwork as model context. Galleries and Website Builder use the same backend Assistant. Avoid entering private client details in a help question.
- Website draft text: when you choose to generate text in Website settings, your business brief and the editable text fields from your current draft go through the shared AI service. Image files, gallery files, link destination settings, account records, and sign-in credentials are not added as generation context. Anything you include in the brief or editable text can be sent. Review the suggestions before applying them to your draft; saving and publishing are separate choices. Avoid putting private client details in the brief or draft text.
The native Quick Add parser remains on-device where that local feature is used. ShootCal does not sell AI inputs or use them for advertising.
ShootCal requests no saved response object for later API retrieval. That does not establish zero retention: OpenAI’s default abuse-monitoring logs may contain prompts and responses and are retained up to 30 days, with legal or safety exceptions. OpenAI states that API data is not used for training by default unless the API customer opts into sharing it. See OpenAI API data controls and response storage.
What data goes to OpenAI for AI setup?
The optional Set up with AI choice on the welcome screen uses OpenAI through ShootCal’s shared AI service. Nothing is sent until you choose AI setup. Then ShootCal sends your replies, your first name and business name, and a short summary of your current setup: your session type names, lengths, and prices, your booking hours and time zone, and yes or no flags for invoice settings, a saved contract, and connected calendars. Relevant ShootCal help material explains the available settings. Your logo is saved to your account and is not sent to the model. ShootCal does not load your client list, appointments, invoices, contract contents, sign-in tokens, or payment details as setup context. Information you type in your replies can itself contain personal data.
The assistant only suggests changes. Each one is saved by ShootCal’s normal settings screens after you approve it. ShootCal keeps the setup conversation for 30 days after your last activity, then deletes it, and also deletes it with your account. The OpenAI data controls described above apply to these requests too, including the distinction between response storage and abuse-monitoring retention.
What happens to messages in the shootcal.com chat?
The optional “Ask about ShootCal” chat on the public website answers questions about ShootCal using OpenAI through ShootCal’s shared AI service. What you type in the chat, together with the chat’s earlier replies, goes through api.shootcal.com to OpenAI to write the answer. The chat is not connected to your ShootCal account and never sees your calendar, clients, paperwork, or Google data.
ShootCal does not store chat messages. Your browser keeps the conversation only while the tab is open, and the server keeps a one-way hash of your network address only as long as it needs to apply the chat’s usage limits. Please don’t enter personal or client details in the chat.
The OpenAI data controls described above apply to public chat requests too. Asking for no saved response object does not establish zero retention.