Configuring business hours and greetings for Zoom Virtual Agent with Zoom Phone

Customers use Zoom Virtual Agent (ZVA) as a voice agent integrated with Zoom Phone to handle inbound consumer inquiries. When end consumers call the phone number listed on the customer’s website, the call is automatically routed from Zoom Phone to Zoom Virtual Agent.

These calls can occur at any time during regular business hours, after hours, or on holidays. Customers can configure ZVA to recognize business hours and greet callers appropriately based on the time of day.

End consumers can also ask about business hours for specific days of the week, such as Are you open on Tuesday? or What are your hours for Friday?.

Requirements for configuring business hours and greetings

Limitations for business hours and greetings for Zoom Virtual Agent with Zoom Phone

Table of Contents

How business hours data is shared with Zoom Virtual Agent

When a call is received, Zoom Phone sends configuration metadata and caller-related metadata to Zoom Virtual Agent. One of these metadata fields is Business Hours (global_system.zp_system.businessHours). This metadata allows Zoom Virtual Agent to determine whether the call occurs during or outside business hours. Admins can create a custom script to parse this metadata, retrieve the current time, day, and date for the site associated with the phone number, and adjust the greeting or response accordingly.

How to configure business hours

Create a custom script

Create a custom script, for example, format_business_hours. This script retrieves the business hours configuration from Zoom Phone and determines whether the store or office is currently open. The script performs the following actions:

Example

function formatTime(timeStr) {
  if (!timeStr || typeof timeStr !== "string") return "";

  var parts = timeStr.split(":");
  var hour = parseInt(parts[0], 10);
  var minute = parts[1] || "00";

  if (isNaN(hour)) return timeStr;
  if (hour === 24) return "midnight";

  var ampm = hour >= 12 ? "PM" : "AM";
  hour = hour % 12;
  if (hour === 0) hour = 12;

  // Use natural words for noon and midnight
  if (hour === 12 && minute === "00") {
    return ampm === "PM" ? "noon" : "midnight";
  }

  // Drop :00 for whole hours
  if (minute === "00") return hour + " " + ampm;

  return hour + ":" + minute + " " + ampm;
}

function timeToMinutes(timeStr) {
  if (!timeStr || typeof timeStr !== "string") return null;

  var parts = timeStr.split(":");
  var hour = parseInt(parts[0], 10);
  var minute = parseInt(parts[1], 10);

  if (isNaN(hour) || isNaN(minute)) return null;
  return (hour * 60) + minute;
}

function parseBusinessHoursConfig(raw) {
  if (!raw) {
    log.debug("It is null");
    return null;
  }
  if (typeof raw === "object") {
    log.debug("Object");
    return raw;
  }
  if (typeof raw === "string") {
    log.debug("It is of the type string")
    try { return JSON.parse(raw); } catch (e) {
      log.error("Failed to parse businessHoursConfig: " + e.message + " | raw: " + raw);
      return null;
    }
  }
  return null;
}

function getCurrentStoreDateTime(timezone) {
  var now = new Date();

  var dateTimeString = now.toLocaleString("en-US", {
    timeZone: timezone,
    weekday: "long",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
    hour12: false
  });

  var parts = dateTimeString.split(" ");
  var weekdayName = parts[0];
  var timePart = parts[1] || "00:00:00";
  var timeParts = timePart.split(":");

  return {
    weekdayName: weekdayName,
    hour: parseInt(timeParts[0], 10),
    minute: parseInt(timeParts[1], 10),
    second: parseInt(timeParts[2], 10)
  };
}

function getTodaySchedule(weeklySchedule, weekdayName) {
  if (!Array.isArray(weeklySchedule)) return null;

  for (var i = 0; i < weeklySchedule.length; i++) {
    if (weeklySchedule[i].day_of_week === weekdayName) {
      return weeklySchedule[i];
    }
  }
  return null;
}

function isStoreOpenNow(todaySchedule, currentMinutes) {
  if (!todaySchedule || !todaySchedule.is_open) return false;
  if (todaySchedule.is_24_hours) return true;

  var openMinutes = timeToMinutes(todaySchedule.open_time);
  var closeMinutes = timeToMinutes(todaySchedule.close_time);

  if (openMinutes === null || closeMinutes === null) return false;
  if (closeMinutes >= 1440) return currentMinutes >= openMinutes;
  if (closeMinutes > openMinutes) return currentMinutes >= openMinutes && currentMinutes < closeMinutes;

  // Overnight hours (e.g. 22:00 – 02:00)
  return currentMinutes >= openMinutes || currentMinutes < closeMinutes;
}

async function getStoreStatus(businessHoursConfig) {
  var timezone = businessHoursConfig.timezone;

  if (!timezone) {
    return { storeStatus: "Closed", currentStoreDateTime: null, todaySchedule: null, reason: "Timezone not provided" };
  }

  var currentStoreDateTime = getCurrentStoreDateTime(timezone);

  if (businessHoursConfig.business_hours_type === "24_7") {
    return { storeStatus: "Open", currentStoreDateTime: currentStoreDateTime, todaySchedule: null, reason: "Store is open 24/7" };
  }

  var todaySchedule = getTodaySchedule(businessHoursConfig.weekly_schedule, currentStoreDateTime.weekdayName);

  if (!todaySchedule) {
    return { storeStatus: "Closed", currentStoreDateTime: currentStoreDateTime, todaySchedule: null, reason: "No schedule found for today" };
  }

  var currentMinutes = (currentStoreDateTime.hour * 60) + currentStoreDateTime.minute;
  var open = isStoreOpenNow(todaySchedule, currentMinutes);

  return {
    storeStatus: open ? "Open" : "Closed",
    currentStoreDateTime: currentStoreDateTime,
    todaySchedule: todaySchedule,
    reason: open ? "Within business hours" : "Outside business hours"
  };
}

function buildGreeting(storeStatusDetails) {
  var intro = "Hi I am Zoom Virtual Agent.";
  var todaySchedule = storeStatusDetails.todaySchedule;

  var openTime = todaySchedule && !todaySchedule.is_24_hours ? formatTime(todaySchedule.open_time) : "";
  var closeTime = todaySchedule && !todaySchedule.is_24_hours ? formatTime(todaySchedule.close_time) : "";
  var hasHours = openTime && closeTime;

  if (storeStatusDetails.storeStatus === "Open") {
    if (!hasHours) return intro + "We are open 24 hours a day, 7 days a week. How can I help you?";
    return intro + "We are open from " + openTime + " to " + closeTime + ". How can I help you?";
  }

  if (!hasHours) return intro + "We are currently closed. How can I help you?";
  return intro + "We are currently closed. Our hours are " + openTime + " to " + closeTime + ". How can I help you?";
}

async function main() {
  var rawConfig = var_get()["business_hours_string"];
  var timezone = var_get()["timezone"];
  var businessHoursConfig = parseBusinessHoursConfig(rawConfig);

  try {
    if (!businessHoursConfig) {
      throw new Error("Invalid or missing business hours configuration");
    }

    var storeStatusDetails = await getStoreStatus(businessHoursConfig);
    var currentStoreDateTime = storeStatusDetails.currentStoreDateTime;
    var todaySchedule = storeStatusDetails.todaySchedule;

    // 1. Current day and time
    var currentDay = currentStoreDateTime ? currentStoreDateTime.weekdayName : "";
    var currentTime = "";
    if (currentStoreDateTime) {
      var h = currentStoreDateTime.hour < 10 ? "0" + currentStoreDateTime.hour : "" + currentStoreDateTime.hour;
      var m = currentStoreDateTime.minute < 10 ? "0" + currentStoreDateTime.minute : "" + currentStoreDateTime.minute;
      currentTime = formatTime(h + ":" + m);
    }

    // 2. Today's open/close hours in user-friendly format ("" when 24-hour or closed all day)
    var openHours = "";
    var closeHours = "";
    if (todaySchedule && !todaySchedule.is_24_hours && todaySchedule.is_open) {
      openHours = formatTime(todaySchedule.open_time);
      closeHours = formatTime(todaySchedule.close_time);
    }

    // 3. Store status
    var storeStatus = storeStatusDetails.storeStatus;

    var greetingMessage = buildGreeting(storeStatusDetails);

    log.info("Greeting message built successfully");

    return {
      currentDay: currentDay,
      currentTime: currentTime,
      openHours: openHours,
      closeHours: closeHours,
      storeStatus: storeStatus,
      greetingMessage: greetingMessage
    };
  } catch (error) {
    var errorMessage = error && error.message ? error.message : String(error);
    log.error("Error building greeting message: " + errorMessage);

    return {
      currentDay: "",
      currentTime: "",
      openHours: "",
      closeHours: "",
      storeStatus: "Closed",
      greetingMessage: "Hi, I am Zoom Virtual Agent, How can I help you?",
      error: errorMessage
    };
  }
}

Enable the pre-init flow

In Zoom Virtual Agent, you can run a flow before the greeting is played. This is called the pre-init flow.
  1. Sign in to the Zoom web portal as an admin.
  2. In the top-right corner, click your profile picture or initials, then click Admin Center.
  3. In the side menu, click Product configuration then AI Studio.
  4. Click Virtual Agents.
  5. Select the voice agent you want to configure.
  6. In the upper right, click the Settings icon , then expand Agent behavior.
  7. Click the Pre-conversation action toggle.
  8. Click Add action to open the flow canvas.

Apply the script in the pre-init flow

After enabling the pre-conversation action, configure the script that will run before the greeting:
  1. In the pre-init flow canvas, drag and drop the Tools widget onto the canvas and configure the following:
  2. Save and publish the flow.
When a call is received, the script runs before the greeting and dynamically adjusts the message based on the business hours configuration.

Set up a skill

You can create a skill in Zoom Virtual Agent to automatically answer questions about business hours. This skill uses the business hours data provided by Zoom Phone to give accurate, real-time responses to customer inquiries.
  1. Sign in to the Zoom web portal as an admin.
  2. In the top-right corner, click your profile picture or initials, then click Admin Center.
  3. In the side menu, click Product configuration then AI Studio.
  4. Click Virtual Agents.
  5. Select the voice agent you want to configure.
  6. In the Skills section, click Create skill.
  7. Enter the following details:
  8. In the Instructions box, enter the skill logic that defines how Zoom Virtual Agent should interpret and respond to business hours queries. See example below.
  9. Click Add.

Example

## Goal ## 

Answer consumer questions about store open and close hours using global_system.zp_system.businessHours

## Data ## 
- Business hours JSON: global_system.zp_system.businessHours
- Today's weekday: global_custom.<>weekdayName (e.g., "Monday")
The JSON includes: 
- business_hours_type: "24_7" or "custom" 
- business_hours_summary 
- weekly_schedule[] with fields: day_of_week, is_open, is_24_hours, open_time, close_time, display_text

## Determine the Target Day ##
Today
  Examples: "Are you open today?" / "How long are you open?"
  Target day: global_custom.<>.weekdayName
Specific day
  Examples: "What are your hours on Friday?" / "Are you open on Wednesday?"
  Target day: Day named by the consumer
Next <day>
  Examples: "Are you open next Monday?" / "What are your hours next Friday?"
  Target day: Day named after "next"

For "next <day>": the weekly schedule repeats, so hours are identical to the matching day_of_week entry. If the named day equals today (e.g., "next Monday" asked on Monday), respond with the hours and note they are the same every week. 

## Execution ##
1. Before looking up hours, always respond first with: "Let me check the hours for you!". This gives the consumer immediate acknowledgment while the data is being retrieved.
2. Check business_hours_type.
3. If "24_7": the store is open 24 hours every day. Answer immediately — do not check weekly_schedule.
4. If "custom": find the weekly_schedule entry where day_of_week matches the target day (case-insensitive).
5. Evaluate the entry:
   - is_open is false → store is closed that day
   - is_open is true AND is_24_hours is true → store is open 24 hours
   - is_open is true AND is_24_hours is false → store is open from open_time to close_time
6. Use display_text as your answer source. You may rephrase it naturally but do not alter times or status.
7. Convert times to 12-hour format in your response (e.g., "09:00" → "9:00 AM", "18:00" → "6:00 PM").
8. Do not guess or infer hours. Only use what is in the JSON.

## Response Style

Be brief and natural. Never expose field names, variable names, or JSON structure.

Examples:
- "We're open today from 9:00 AM to 6:00 PM."
- "On Tuesday we're open 24 hours."
- "We're open 24 hours every day."
- "We're closed on Sundays. Would you like to know hours for another day?"
note icon
This article applies to Zoom Virtual Agent voice agent. If you are using other Zoom Virtual Agent types, such as chat agent or classic chatbot, refer to their respective documentation for setup and channel deployment instructions.