Skip to content

Online booking and availability ​

On the web, open Booking Setup to manage session types and their booking page together. Offer at least one session type for online booking, then choose your Primary Business Calendar: ShootCal's own calendar works with no provider connected, or you can use a connected Google or Microsoft calendar. Either way ShootCal uses it as the baseline for availability and saves new online bookings as ShootCal appointments in your account. Review your business details and choose Save booking page. Your first successful setup save makes booking available. Opening the page or following its walkthrough does not publish anything.

If you have no session types yet, the page shows your saved logo above faded example cards, or an Add logo placeholder. Click the example cards to add your first session type with the walkthrough, or choose Add manually. Example cards are illustrations only; they do not add content to your account. The booking walkthrough also offers first-session creation and can continue through all eight steps before you add one.

Use Pause booking when you need to stop accepting bookings through previously shared links or embeds. All settings remain visible and editable while paused. Saving those changes keeps the page paused; Resume booking makes it available again.

The quick walkthrough covers session types, thumbnails, the Primary Business Calendar, the main booking rule, business details, saving, sharing, and requests. It skips advanced settings. Booking rules, calendar selection, business details, redirects, and blackout dates all use Save booking page. Save or undo your draft before leaving the booking page.

What blocks online booking? chooses whether timed calendar appointments hold their time immediately (Calendar events) or wait until marked deposit received or confirmed (Confirmed sessions only). With Confirmed sessions only, unpaid or tentative appointments you create manually stay bookable. Accepted booking requests and instant bookings hold their time even before payment. Use blackout dates for days off.

In ShootCal Settings, choose your logo and review the full-image preview. A transparent PNG 500 px wide × 100 px tall works well on the booking page. Square 500 × 500 px logos and proportions in between also work. Choose Use full image to keep the original shape, or Crop (optional) to make a wide version.

Existing square logos have more room to display automatically. Previously saved crops keep their saved artwork; re-upload the original if you want to restore anything cropped away. A logo is optional and is not required to schedule sessions.

Choose which calendars block availability ​

In the Calendar sidebar, Show on my calendar controls which events you see. Open Booking availability there to choose additional calendars that should block booking times. These choices are independent: a calendar can stay visible for awareness without blocking, or block availability while hidden from your calendar view.

The Primary Business Calendar remains the baseline and follows your booking rules. Additional blocking calendars are off by default. Select only calendars whose busy events mean you are unavailable. For example, you can keep a child's sports calendar visible without letting every practice block your photography sessions. Selected extra calendars block busy timed and all-day events; events marked free do not block. If a selected calendar cannot be checked, ShootCal does not assume those times are available. These private conflicts do not count as paid photography sessions or expose event details.

Request first is a separate choice: it lets you review and approve requests even when the availability calendar is accurate.

By default, the public availability calendar follows your booking rule. In Advanced settings, Use a different rule for the public calendar display lets you choose what appears booked separately. For example, you can show only confirmed sessions publicly while calendar appointments still hold their time. The public day overview and final time-slot checks use their respective rules. Event names and details stay private. Existing separate rules remain selected until you change them; unchecking the option makes the display follow your booking rule again. Save to apply changes.

The booking page leads with your session types, each as a card with its thumbnail photo, length, and optional price. Once a client picks the session they want, a compact availability calendar shows which days are open for that type: it already knows the type’s bookable weekdays, booking window, lead time, and per-day limit, so the client is never guessing dates. For types that publish times, tapping an open day shows the real open times (golden-hour types calculate them from the actual sunset or sunrise at your location for that exact date; studio types show your fixed hourly slots). Contact-first and all-day types take a date request instead of a time. And any client can switch to Flexible dates and pick a date range that could work (tap a start and an end date), or send the request with no dates at all, and the range they picked is recorded on the request.

The availability calendar shows which days are open, limited, or fully booked. It is private by design. It never shows who booked you, what the shoot is, or where it is. With Confirmed sessions only, it publishes sessions marked deposit received or confirmed. Calendar events uses busy events instead, unless you select a separate public display rule. You can embed on your own website (Squarespace, Wix, etc.) by pasting the one-line embed snippet from Booking Setup (older iframe snippets keep working), or share the link directly. On a ShootCal Website you don't paste anything: the Availability section places the same embed script automatically. Your website embed opens the same session-type-first booking flow as your shared link.

Under Put booking on your website, choose Light, Dark, or Match website, then save the booking page. Light is the default. Choose separate Light background and Dark background colors, or reset either to its default. The light form has a soft shadow; the dark form has a thin border. Text and controls adjust to keep custom background colors readable. These settings apply on your own website and on ShootCal Websites.

Match website follows the appearance of the page containing the current embed script and updates without clearing a visitor's in-progress details. A booking page opened on its own follows the visitor's device appearance when Match website is selected. Older raw iframe embeds still work, but replace them with the current embed code to follow the containing website's appearance. The ShootCal website builder's Availability block links to these settings in a new tab, so you manage the form in one place.

By default, a submission does not book anything. It creates a request that lands in your Booking Requests inbox (and emails you, unless you choose to handle requests only in the app). You review it and either Accept (which saves the confirmed ShootCal appointment right then) or Decline. Each session type has one of three booking modes: Request first (the default just described); Instant book, where the picked time is confirmed and added to your calendar immediately with no manual step; and Contact first, which takes a date request with no published times (a contact-first type can never instant-book).

A few built-in protections: a hidden anti-bot field, a 5-requests-per-hour-per-IP rate limit, and a re-check at accept time that the slot is still free so you never double-book.

The app presents one booking-page experience. Behind it, existing routes remain compatible so previously shared links and calendar subscriptions keep working:

  • The availability calendar (read-only). Served at /embed/<token> and rendered to standalone HTML. Its data source is a stored calendar feed file on disk, built from your ShootCal appointments. The embed reads that file directly: no database lookup for the events, and no live provider call when it renders. There is also a raw feed URL (the same file, served as a static .ics) for calendar subscriptions. When you have booking enabled, /embed/<token> serves the booking flow by default (it redirects into /book with embed styling); ?view=calendar renders the classic grid, which is also what a booking-disabled feed always renders.
  • The booking page (interactive). Served on book.shootcal.com: the short address is book.shootcal.com/<name>, and book.shootcal.com/book/<name> is the same page, with companion endpoints for a month’s open days per session type and a day’s open times, plus a submit endpoint. Older shootcal.com/<name> and shootcal.com/book/<name> links redirect there permanently, so nothing you already shared breaks. The month view is computed from the type’s rules plus the same published feed the calendar uses: advisory, with the real re-checks at day-pick and submit. The address ShootCal shows you follows your one account-wide ShootCal URL, which you manage in Settings and which is also used for your gallery and website addresses. An internal random token and any booking-only alias created before the account-wide URL continue to resolve as compatibility routes so already-shared links do not break; neither is a second editable public identity.

For older integrations that explicitly requested the classic calendar view, the compatibility renderer and the booking page remain connected: day cells are clickable when booking is enabled. Open days link straight to that date on the booking page; a fully booked day links with a flag so the booking page can say ‘this date is full, but if your dates are flexible I can try to fit you in.’

Why the availability calendar can never leak client details ​

The privacy guarantee is enforced where the feed is built, not at display time. Two mechanisms:

  1. Only booked days are published. A day is ‘booked’ only if its session’s deposit is received. That check reads the deposit marker stored on the ShootCal appointment. Personal events, tentative holds, and unpaid sessions never appear. (There is also an opt-in ‘all busy events’ mode for a brand-new user who hasn’t started marking deposits; even then no event text is included.) 2. Each published event carries only timing. Each event in the feed includes only its identifier and start/end times and a busy status. There is no title, location, description, attendee, or organizer.

This is belt-and-suspenders: before the file is written to disk, ShootCal scans the generated text for any of a list of forbidden properties (title, description, location, attendee, organizer, contact, url, and similar). If any is found, the build fails closed and the old file is left untouched. The event identifier is also sanitized of line breaks so a malformed id can’t smuggle a property line in. So even the feed that ships contains nothing but busy blocks.

How a day is colored Open / Limited / Booked ​

The embed renders ONE calendar from an opaque busy feed that has no per-type information, so it can’t color per type. Instead it resolves a single per-day capacity equal to the MAX capacity across your bookable session types, and a day reads ‘Booked’ only when even the most permissive type is full. (The visible legend in the embed labels these states Available / Limited / Booked.) Concretely:

  • A studio type’s ceiling is its back-to-back slot count derived from its fixed hours (open, close, session length, turnover); you never hand-count slots (‘Fill’).
  • A sunset/sunrise type’s ceiling is the cascade maximum of six slots.
  • A fixed-clock-time type is always 1.

Your per-day limit for a type (or the studio default when the type inherits) is clamped to that physical ceiling. The cap honors which types are actually bookable, using the same bookability rules as the booking page, so the calendar can’t advertise Open or Limited for a type the request page won’t book. If types exist but none are structured-bookable (for example only all-day weddings), the calendar colors booked-only (capacity 1).

The day’s open times: golden-hour cascade vs studio grid ​

Both the slot picker and the submit path use the same slot-planning logic (the same logic runs in the native app and the web app), so the picker and the server agree exactly. By session-type mode:

  • sunset / sunrise: ShootCal computes the actual sunset or sunrise time for that date at your stored latitude and longitude. It then packs sessions back from sunset (or forward from sunrise) in session-length plus break steps, routing AROUND your existing calendar events, up to six candidate slots. The first slot anchored at the sun event is flagged as prime (the ‘Golden hour’ tag on the page). This requires a location; with none, sun types yield no slots.
  • studio: tiles your fixed open-to-close hours in session-length steps plus turnover gaps; every tile is a slot, and a tile overlapping a busy event is flagged as already booked.
  • fixed: a single clock time.
  • all-day: produces no timed slots, but the type can still be offered on the booking page as a full-day date request (the contact-first flow, with flexible-date preferences).
  • manual / empty mode: no structure, so nothing bookable.

An explicit Block booking availability choice on a Personal Event blocks its dates and times in either booking mode. New Personal Events default to allowing bookings. Every candidate is checked against your Primary Business Calendar using What blocks online booking?. Calendar events blocks timed appointments; Confirmed sessions only leaves unpaid or tentative appointments you create manually open. Accepted booking requests and instant bookings still hold their time before payment, so the public day and time pickers do not offer that slot again. Selected additional blocking calendars contribute their busy times independently. Personal event details never appear in the public booking page.

The crucial distinction: public availability is capped, the manual picker is not ​

This is the heart of the ‘public vs manual’ difference. For a sunset/sunrise type, the full cascade can produce up to six candidate slots, but the PUBLIC booking page does not publish all of them. For sun types it figures out how many slots remain (your per-day limit minus what is already booked that day, and a deposit-paid session you scheduled by hand counts as booked too) and publishes only that many open slots. With the default per-day limit of one, the public page offers exactly ONE golden-hour slot (the prime time), even though the cascade physically fits more.

That cap is deliberate: it makes the public page match the day-full gate and the calendar’s per-day coloring. The full multi-slot cascade is the MANUAL tool: it’s there for you (in the app or manual entry) to stack several sessions into one golden hour when you choose to, but the public self-booking flow won’t oversell your evening. Studio and fixed types instead offer all their slots (a booked one is shown and flagged), because their per-slot availability already gates capacity.

Your per-day limit is enforced again at submit time: ShootCal counts the accepted bookings of that type on the day (in your calendar’s timezone) and the deposit-paid sessions already on your calendar, and takes whichever count is higher (an accepted booking is itself one of those paid events, so nothing is counted twice); a request over the limit is rejected as ‘day full’. Studio ‘Fill’ types leave the day-count uncapped and let per-slot availability do the gating.

Request → review → accept (the default flow) ​

By default booking is request-mode and a public submission NEVER creates a calendar event. The submit step validates everything, then for request-mode stores a pending request with the picked start and end times. The slot is left OPEN while pending. It then best-effort emails you (the request is saved even if the email fails).

The pending request shows up in two places, both of which call the same accept/decline logic:

  • The magic-link page in the notification email, with accept and decline buttons. This page is hardened against being embedded in a frame (it shows client personal information), so other sites cannot iframe it.
  • The Booking Requests inbox in the web app and the iPhone and iPad apps, which lists requests (pending first, soonest slot first) and offers the same accept and decline, plus replying to the client (prefilled from the type’s saved reply) and a decline-with-message option; on the web you can also ‘Accept with changes’ by adding the event yourself, and the request is recorded as booked. A request you never answered whose requested date has already passed is tagged Not replied · Past date and moves out of Pending into the Recently replied or declined group (it no longer counts toward the Requests badge); you can still reply, add it to the calendar if the shoot happened, or decline it to clear it. The Android app lists pending requests for reference; choose Respond on ShootCal website to reply, accept, or decline on the web.

Accept re-validates that the slot is still free, then creates the confirmed ShootCal appointment (the client's name, email, phone, and notes stay in ShootCal with the booking), and marks the request accepted. Declining can optionally send the client your saved decline note (with the client and session type filled in) before marking the request declined; either way nothing was ever booked, so there’s nothing to unwind. A declined request can be reopened to pending later (a sent decline email is not recalled on reopen).

Instant mode, and where everything is stored ​

Instant mode is opt-in and resolved per session type (it reads the type’s booking mode, falling back to your global setting). For security it is ALWAYS derived from the resolved type, never from a field in the client’s submission, so a tampered request can’t flip a request-only type into a booking. In instant mode ShootCal claims the slot in its own database first (as accepted, with no appointment yet) inside an atomic database transaction that re-checks the per-day limit and slot overlap, THEN creates the ShootCal appointment and links it. If the appointment creation fails, the claim is deleted so the slot reopens.

Where data lives (server-side, scoped to your account). The main items:

  • Your booking-page settings: which calendar, lead time, how far ahead bookings open, your per-day limit, which days and session types are bookable, the booking mode, blackout dates, your business name, and your after-booking redirect URL. Your account-wide ShootCal URL is managed separately in Settings. ShootCal also retains the booking page’s internal random token and, for an older account that used one, its legacy booking alias so existing links keep working; they are compatibility routes, not editable booking settings. (Client confirmation emails are a per-session-type rule, on by default, stored with your synced session types.)
  • Every request and instant-book: its status (pending, accepted, declined, responded, or canceled), the slot times, the client’s name, email, phone, and notes, the request’s secret token, the linked appointment, decision and response timestamps, and any flexible-date preferences.
  • A lightweight audit and rate-limit record per submission: which account it belongs to, the visitor’s IP hashed with the app’s secret key (never the raw IP), the client-supplied email (empty for demo and rate-limit-only rows), the slot start, and a timestamp. That IP hash is what enforces the per-hour-per-IP limit.
  • Your synced session types and studio defaults (which override the legacy global rule settings), including each type’s booking rules, reply templates, and your decline reply.
  • The availability feed file used by the embed.
  • The optional studio logo shown atop the embed.
  • Optional per-type cover photos, stored server-side and shown on the booking page.

Anti-abuse, self-healing, and the reply tracking path ​

Several safeguards run on the public path:

  • Honeypot: a hidden field a real person never sees. A bot that fills it gets a fake success and the submission is silently dropped (no stored request, no email, no calendar write).
  • Rate limit: 5 requests per hour per IP, keyed on the visitor’s IP hashed with the app’s secret key (the IP comes from Cloudflare’s connecting-IP header, falling back to the raw connection address). It even logs a row for the public demo page (with an empty email) so the cap applies there too.
  • Demo page (the shootcal.com availability try-it flow): runs every validation for real but stores no request and sends no email, so it can’t be used as a mail relay.
  • Re-validation on accept/instant-book: an atomic database transaction and overlap check recheck the slot before an accepted or instant booking is saved. A stale booking draft must be refreshed and reviewed before retrying.
  • Consistent gating: availability checks saved ShootCal appointments and any selected external busy calendars. A confirmed cancellation releases the appointment’s local block; a connected calendar copy may still block while cancellation sync or review is pending. Changes made directly in a connected Google or Microsoft calendar are imported for your review rather than silently changing your bookings.

Notifications: by default you’re emailed each request. A request notification can provide a private reply address that routes your answer back to the client through ShootCal. You can also set reply-mode to ‘app’, which sends no notification email and lets you handle requests in the inbox. See How email works for sending status and reply details.

FAQ ​

Can a member of the public ever see who I’m shooting, or what the shoot is, from my availability calendar? ​

No. The published feed only contains each event’s identifier, start and end times, and a busy status: no title, location, description, or attendees. A forbidden-property audit runs before the file is written and fails closed if any of those ever appear. The published busy dates follow your selected public-calendar display rule. With the confirmed-only rule, tentative appointments do not appear booked; event details stay private with either rule.

If I have a personal appointment on my calendar, will it block a client from booking over it, even though it’s not published? ​

New Personal Events allow bookings by default. Turn on Block booking availability in the event editor to block those dates and times, including all-day and multi-day trips, without publishing the event title or details. Turn it off to allow bookings; paid and accepted bookings still keep their reservations.

Older Personal Events show Existing booking rules until you choose Allow bookings or Block bookings. Their previous behavior follows What blocks online booking?: Calendar events blocks busy timed appointments, while Confirmed sessions only leaves unpaid manually created appointments open. A different calendar can still block through Booking availability in the sidebar, independently of whether it is visible. Imported recurring schedules retain their existing rules and are managed in Google Calendar or Outlook.

Does submitting the booking form put the event on my calendar automatically? ​

In the default request mode, no. The submission creates a pending request and emails you; the slot stays open. You Accept (which creates the event then, re-validating the slot is free) or Decline. Contact-first types work the same way, just without a published time: the client requests a date (with flexible-date preferences) and you follow up. Only if a session type is set to instant mode is the confirmed event created immediately, and that mode is resolved from the type on the server, never from the client’s request; a contact-first type can never instant-book.

Why does my public golden-hour booking page show only one time, when my session can fit several back-to-back? ​

The cascade can fit up to six sun-anchored sessions, but the page caps what it PUBLISHES to your per-day limit minus what’s already booked that day. With the default limit of one, only the prime golden-hour slot is offered publicly. The full multi-slot cascade is the manual tool for you to stack sessions yourself; the public page is intentionally capped so it can’t oversell your evening, and so it matches the day-full gate and the calendar’s coloring. Raise the per-day limit on the type to publish more.

What stops two people from booking the same slot at the same instant? ​

Both the instant-book path and the accept path do the overlap check and claim the slot in a single all-or-nothing step. The second accept waits its turn, sees the first’s now-accepted booking, and bails. One slot can never become two appointments. ShootCal trusts its own record of who’s booked here, because a connected calendar’s sync can lag a moment and a pending request isn’t booked yet.

I deleted an accepted booking directly in my connected calendar. Will that slot stay blocked forever on my booking page? ​

Deleting the synced copy in Google or Microsoft does not delete the ShootCal appointment. ShootCal’s stored appointment is the record your booking page follows. The provider-side change shows up as a pending calendar review on that appointment, where you can accept the cancellation or keep your ShootCal schedule. Cancel the booking in ShootCal, then check the saved result and any pending sync review. The local block clears when cancellation is confirmed; a selected external calendar can still block the slot until its copy is canceled and refreshed.

How is the per-day capacity decided for the single embedded calendar, given it has no per-type data? ​

The embed colors the one calendar by the MAX capacity across your bookable session types (a day is ‘open’ if ANY type has room). Studio capacity is derived from its fixed-hours grid, sun types use the cascade max of six, fixed types are one; your per-day limit (or studio default) is clamped to that physical ceiling. It also respects which types are actually bookable, mirroring the booking page, so it can’t show Open for a type the request page won’t fulfill.

Is the public booking endpoint protected against spam and being used as a mail relay? ​

Yes, several ways. There’s a hidden honeypot field (a bot that fills it gets a fake success and the submission is dropped). There’s a 5-per-hour-per-IP rate limit keyed on the visitor’s IP hashed with the app’s secret key (the IP is taken from Cloudflare’s connecting-IP header, falling back to the raw connection address). Notification emails never go to the client-supplied address on a request (they go to you). A pending request books nothing and emails the client nothing. Only once a booking is actually confirmed (you accept it, or an instant-book type books it) does ShootCal email the client their confirmation, and only when that session type’s confirmation setting is on. The shootcal.com demo availability page runs all checks but stores no request and sends no email, so it can’t relay mail either.

Your current public name is the single account-wide ShootCal URL you choose in Settings. ShootCal uses that name for the booking page, galleries, and website rather than giving the booking page another editable slug. Every booking page also has an internal random token, and an older account may retain a booking-only alias that it shared before the account-wide URL existed. Those older token and alias addresses still resolve to the same enabled booking page so saved bookmarks and links do not suddenly break, but they are compatibility routes: they are not additional names to manage. The address shown in the app follows your current ShootCal URL, as book.shootcal.com/<your name>.

Can the availability calendar and booking page be embedded on Squarespace or Wix? ​

Yes. The snippet is now a single script tag that injects the frame and keeps it sized to the content (previously pasted iframe snippets keep working, since the embed still posts its height to the parent so those auto-size too). It’s edge-cached (10 minutes) and refreshed when your calendar changes. The embed opens the session-type-first booking flow. The booking page also allows framing, but the photographer-only magic-link review page is hard-blocked from framing because it shows client personal information.

Can a client request flexible dates or a date range instead of picking a time? ​

Yes. Any client can switch the booking page to Flexible dates, tap a start and an end date to mark the range that could work, or send the request with no dates at all. The range they picked is recorded on the request, so when you review it you can offer a day that fits. Contact-first and all-day types (like weddings) always work this way: a date request with those same flexible-date preferences, rather than a published time. ← Back to Help Center