Skip to main content

Working Hours

Audience: Sales · Support · QA · Engineering · Management · Where in app: Settings → Working Hours (organization) and per-user availability · Plan availability: All plans

Working hours decide when the widget is online — humans available to take chats and calls — versus offline, where chat falls back to an email-capture flow and calls fall back to a missed-call notification or the AI agent. The launcher's heading/subheading copy and which channels actually connect all depend on this online/offline state.

What it does

  • Computes whether the team is "online" right now, per the configured schedule and timezone.
  • Swaps the launcher copy between online and offline text.
  • Routes chat/calls differently when offline (email fallback, callback, or AI).
  • Drives missed-call notifications when a visitor tries to call outside hours.

How it works

Where hours live

LevelWhereControls
Organizationcompanies.working_hours[] + companies.timezoneDefault availability for the whole company
User (team member)users.working_hours + users.timezonePersonal availability — an agent appears online only when both org and personal hours match

Agency-managed accounts may have the agency override defaults; the working-hours editor shows whose hours are active.

Schema

companies.working_hours = [
{ "day": "Monday", "start_time": "09:00", "end_time": "17:00" },
{ "day": "Tuesday", "start_time": "09:00", "end_time": "17:00" }
]
companies.timezone = "Pacific/Auckland"
companies.timezone_offset = 720 // minutes — denormalized convenience field

day is the full weekday name ("Monday", "Tuesday", …). A day with no entry means unavailable that day.

How online status is computed

The widget computes isOnline client-side (checkIsWithinWorkingHours):

  1. Compute the current date/time in companies.timezone (default UTC if unset).
  2. Find today's row in working_hours[] by full weekday name.
  3. If start_time ≤ now ≤ end_time, online; otherwise offline.

Important defaults from the code:

  • No working hours configured at all → always online. An empty schedule does not mean "closed"; it means "no restriction."
  • No row for today's weekday → offline for that day.
  • On any error computing hours → defaults to online (fail-open).

The backend re-runs equivalent logic server-side for any decision that matters (routing, AI takeover, etc.), so the client value is a display/UX hint, not the security boundary.

Configuration & options

SettingFieldNotes
Org schedulecompanies.working_hours[]Array of {day, start_time, end_time}
Org timezonecompanies.timezoneIANA name (e.g. Pacific/Auckland) — source of truth
Offset (denormalized)companies.timezone_offsetMinutes; convenience only
User scheduleusers.working_hoursPer-agent
User timezoneusers.timezonePer-agent
Online copyonline_title_text, online_title_text_sub_headingLauncher when online
Offline copyoffline_title_text, offline_title_text_sub_headingLauncher when offline
Offline chatchatbot_offline_message, chatbot_offline_questions[]Email + question capture
Inbound AI callai_inbound_call.enableRoute offline calls to AI
Missed-call emailnotifications.missed_callTriggers when a visitor calls outside hours

Online mode behavior

  • Launcher shows online_title_text + subheading.
  • Chat connects directly to a human if chat routing is set to human (otherwise per AI config).
  • Calls ring active agents.
  • AI auto-trigger and call auto-trigger fire per their URL rules.

Offline mode behavior

  • Launcher shows offline_title_text + subheading.
  • chatbot_offline_message and chatbot_offline_questions[] capture an email + question.
  • Call buttons can fall back to a callback request, or route to inbound AI if ai_inbound_call.enable.
  • notifications.missed_call email fires when a visitor tries to call outside hours.

Behaviors & edge cases

  • Empty schedule = always online (fail-open). This surprises admins who clear all rows expecting the widget to go offline — it does the opposite.
  • Error fallback = online. A malformed timezone or schedule defaults to online rather than hiding availability.
  • DST is handled correctly via the IANA timezone name; timezone_offset is not the source of truth and can drift across DST.
  • No holiday calendar. There is no built-in holiday feature. Admins either clear working_hours rows for the affected day(s), or use a hide_on_pages rule during a closure window.
  • Visitor timezone is also detected (for friendly "we're online" messaging), but online/offline is computed against the company timezone, not the visitor's.
  • Offline still installed. "Offline" is a normal state, not a broken install — the widget is present and capturing leads.

Plan & limits

  • Working hours are available on all plans.
  • Per-user (team-member) availability requires the company to have team members; effective availability is the intersection of org and personal hours.
  • AI inbound-call fallback requires the AI calling feature to be enabled (verify per-tier gating).

Technical implementation

  • Online computation: checkIsWithinWorkingHours() in widget/src/stores/widget.ts; consumed in fetchCompany() to set isOnline.
  • Timezone helpers: widget/src/utils/timezone.ts.
  • Types: widget/src/types/company.tsWorkingHour, companies.timezone.
  • Admin UI: organization working-hours editor under Settings → Working Hours (frontend settings).
  • Nox: can answer "is the team online right now?", "what hours is the team scheduled?", and "when was someone last on-call?" using working_hours and users.last_active_at. Nox cannot edit hours — an admin must do that in Settings.
  • See the engineering service page: /services/widget.