Build a starter

This document explains how to build a starter that allows your app or service to notify Google Workspace Studio when an event occurs and initiate a flow execution. In the API, starters are called workflowTriggers.

A starter begins a flow while a step is a single task in the sequence of tasks that encompass a flow. By building a starter, you enable users to set up automated flows that react to real-time events from your app or service.

Building a starter involves declaring the starter in the add-on manifest file and implementing lifecycle callbacks in Google Apps Script, or firing the starter by posting payloads to the Google Workspace Studio API endpoint.

Prerequisites and OAuth authorization

To communicate with the Workspace Studio API endpoint, your app or service must authenticate using OAuth 2.0. The app must request the following dedicated OAuth scope from users during authorization:

https://www.googleapis.com/auth/workspace.studio.trigger

This scope authorizes the app to call the Workspace Studio API and fire flows that the user has configured for that starter.

Offline access and refresh tokens

Because starters notify Workspace Studio asynchronously when an event occurs in the external service—which might happen hours, days, or months after a user configures a flow—your service must provide a valid OAuth 2.0 access token when calling the API endpoint.

The access token provided by Google in the add-on event object (such as during starter configuration or lifecycle callback requests) is short-lived and only valid for 1 hour. It's not sufficient for firing starter events asynchronously in the future. To call the Workspace Studio API over time, your service requires an offline refresh token to generate fresh access tokens on demand.

How you handle authorization and obtain a refresh token depends on your add-on runtime:

  • HTTP add-ons (alternate runtimes): For HTTP add-ons, your backend service must implement a separate OAuth 2.0 authorization flow independent from the built-in add-on authorization to request offline access (access_type=offline) and receive a refresh token.

    You can prompt users to authorize this connection by displaying a sign-in or authorization card when the user configures the starter in Workspace Studio. For more information on returning authorization cards and handling the OAuth flow, see Connect your Google Workspace add-on to a third-party service (treating Google Workspace as the third-party service you connect to).

    Your backend service must store the refresh token securely (for example, in your service's database alongside the triggerId) and use it to retrieve a new access token whenever an event occurs before sending requests to the starter's notifyUri or the triggers.fire API endpoint.

  • Google Apps Script add-ons: Google Apps Script-based add-ons that use scheduled (time-driven) triggers to poll for events can skip implementing an independent OAuth flow. Because scheduled triggers run directly within the Google Apps Script runtime environment, Google Apps Script automatically manages and refreshes the OAuth tokens using the scopes declared in the manifest.

Define the starter in the manifest file

To define a starter, add it to your add-on manifest file (appsscript.json) within the addOns.studio.flows.workflowElements block. This configuration is required for both Apps Script and HTTP runtimes (alternate runtimes). Configure the element as a workflowTrigger instead of a workflowAction (which is used when defining a step). For more information, see Manifest structure for Google Workspace add-ons.

Inside the workflowTrigger block, specify:

  • inputs: Variables the user configures on the configuration card (such as project name, resource filter, etc.).
  • outputs: Variables returnable by the starter to downstream steps in the flow.
  • onConfigFunction: The name of the callback function that displays the user configuration interface.
  • onManageFunction: The name of the callback function invoked by Google to handle starter subscription creation and deletion.

The following code sample shows an example manifest definition for an event starter:

JSON

{
  "timeZone": "America/Los_Angeles",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "addOns": {
    "common": {
      "name": "Trigger App",
      "logoUrl": "https://fonts.gstatic.com/s/i/short-term/release/googlesymbols/start/default/24px.svg",
      "useLocaleFromApp": true
    },
    "studio": {
      "flows": {
        "workflowElements": [
          {
            "id": "triggerDemo",
            "state": "ACTIVE",
            "name": "Event Trigger",
            "description": "Fires when a event occurs in the app.",
            "workflowTrigger": {
              "inputs": [
                {
                  "id": "projectId",
                  "description": "The project identifier to watch.",
                  "cardinality": "SINGLE",
                  "dataType": {
                    "basicType": "STRING"
                  }
                }
              ],
              "outputs": [
                {
                  "id": "eventName",
                  "description": "The name of the triggered event.",
                  "cardinality": "SINGLE",
                  "dataType": {
                    "basicType": "STRING"
                  }
                },
                {
                  "id": "eventMessage",
                  "description": "Detailed event message description.",
                  "cardinality": "SINGLE",
                  "dataType": {
                    "basicType": "STRING"
                  }
                }
              ],
              "onConfigFunction": "onConfigTrigger",
              "onManageFunction": "onManageTrigger"
            }
          }
        ]
      }
    }
  }
}

Handle the starter subscription lifecycle

When a user configures and enables a flow containing your starter, or if the flow is disabled or deleted, Google calls your add-on using the onManageFunction callback function declared in the manifest.

The lifecycle event object

The callback function receives a WorkflowEventObject containing the action context. For starters, this includes:

  • Trigger Creation (event.workflow.triggerCreation): Fires when the flow is published or enabled.

    • triggerId: A unique UUID string identifying this starter registration instance.

    • notifyUri: The unique REST API endpoint URL associated with this starter registration (for example, https://workspacestudio.googleapis.com/v1/triggers/YOUR_TRIGGER_ID:fire).

    • inputs: The variable inputs configured by the user from the card.

  • Trigger Deletion (event.workflow.triggerDeletion): Fires when the starter is removed from the flow, or when the entire flow is disabled or deleted.

    • triggerId: The unique UUID string of the subscription instance to clean up.

Alternate Runtimes (HTTP API) subscription lifecycle

For add-ons built using alternate runtimes, subscription lifecycle notifications are delivered using HTTP POST requests to the add-on's configured HTTP endpoint URL with the action name specified by the onManageFunction callback function. The payload matches the JSON representation of the WorkflowEventObject.

For more information on alternate runtimes, see Build a Google Workspace add-on using HTTP endpoints.

Implement lifecycle callbacks in Apps Script

The following Apps Script example shows how to configure the user interface card, handle subscription lifecycle events using onManageTrigger, and fire the starter request back to Google when an event occurs.

Apps Script

/**
 * Generates and returns the user configuration card to collect inputs.
 */
function onConfigTrigger() {
  const projectInput = CardService.newTextInput()
    .setFieldName("projectId")
    .setTitle("Project ID")
    .setHint("Enter the project identifier to watch");

  const section = CardService.newCardSection()
    .setHeader("Configure Event Trigger")
    .addWidget(projectInput);

  const card = CardService.newCardBuilder()
    .addSection(section)
    .build();

  return card;
}

/**
 * Handles subscription lifecycle events sent from Google Workspace Studio.
 *
 * @param {Object} event The Workspace Studio event object.
 */
function onManageTrigger(event) {
  const triggerCreation = event.workflow.triggerCreation;
  const triggerDeletion = event.workflow.triggerDeletion;

  if (triggerCreation) {
    const triggerId = triggerCreation.triggerId;
    const notifyUri = triggerCreation.notifyUri;
    const inputs = triggerCreation.inputs;

    // Extract input values configured by the user.
    const projectId = inputs["projectId"].stringValues[0];

    // TODO: Save triggerId, notifyUri, and projectId in your database/service.
    // Your backend service listens for events related to 'projectId'
    // and calls notifyUri when those events occur.
    console.log("Trigger subscription created: " + triggerId +
                ", Notify URI: " + notifyUri +
                ", Match Project: " + projectId);

  } else if (triggerDeletion) {
    const triggerId = triggerDeletion.triggerId;

    // TODO: Remove references to triggerId from your database and stop
    // sending future event notifications to the associated notifyUri.
    console.log("Trigger subscription deleted: " + triggerId);
  }
}

/**
 * Mock function showing how your backend service fires the trigger.
 * This logic runs on your service when a watched event occurs.
 *
 * @param {string} notifyUri The stored notifyUri associated with the trigger.
 * @param {string} triggerId The stored triggerId.
 * @param {string} userAccessToken The OAuth 2.0 access token for the user
 *     (obtained using your stored refresh token).
 */
function simulateEventFire(notifyUri, triggerId, userAccessToken) {
  // A unique UUID version 4 is recommended as the requestId for idempotency.
  const requestId = Utilities.getUuid();

  const payload = {
    "name": "triggers/" + triggerId,
    "outputs": {
      "eventName": { "stringValues": ["EventOccurred"] },
      "eventMessage": { "stringValues": ["Hello from the service!"] }
    },
    "requestId": requestId
  };

  const options = {
    "method": "POST",
    "contentType": "application/json",
    "headers": {
      "Authorization": "Bearer " + userAccessToken
    },
    "payload": JSON.stringify(payload),
    "muteHttpExceptions": true
  };

  const response = UrlFetchApp.fetch(notifyUri, options);
  const responseCode = response.getResponseCode();

  if (responseCode === 200) {
    console.log("Trigger successfully fired!");
  } else if (responseCode === 404) {
    // 404 means the trigger registration is invalid or deleted.
    console.log("Trigger not found. Stop sending events for this trigger.");
    // TODO: Clean up the trigger from your backend database.
  } else if (responseCode === 429 || responseCode >= 500) {
    console.log("Temporary error (" + responseCode + "). Retry using exponential backoff.");
  } else {
    console.log("Failed to fire trigger. HTTP Code: " + responseCode + " - " + response.getContentText());
  }
}

Use the Workspace Studio API

You can use the Workspace Studio API (workspacestudio.googleapis.com) to programmatically notify Google of starter events.

The endpoints reside under the base path: https://workspacestudio.googleapis.com/v1.

Notifies a starter event

Fires a starter using the triggers.fire method to initiate the execution of a flow.

  • HTTP Method: POST
  • Path: /v1/triggers/{triggerId}:fire (where {triggerId} is the unique identifier retrieved during trigger subscription creation)
  • OAuth scope: https://www.googleapis.com/auth/workspace.studio.trigger

The following code sample shows how to fire a starter in the request.

Request

{
  "name": "triggers/TRIGGER_ID",
  "outputs": {
    "eventName": {
      "stringValues": [
        "EventOccurred"
      ]
    },
    "eventMessage": {
      "stringValues": [
        "Hello from the service!"
      ]
    }
  },
  "log": {
    "textFormatElements": [
      {
        "text": "An event occurred in the app."
      }
    ]
  },
  "requestId": "UNIQUE_REQUEST_ID"
}
  • name (string, required): The resource name of the starter, formatted as triggers/{triggerId}.
  • outputs (map, optional): A map of starter output variables representing the event data. Each value is a VariableData object supporting typed lists (such as, stringValues, booleanValues, integerValues).
  • log (object, optional): A TextFormat markup representation shown in the Workspace Studio execution activity logs.
  • requestId (string, optional): A unique identifier (UUID v4 recommended) of up to 36 ASCII characters to ensure API idempotency on retries.

Response

The response returns an empty JSON object {} on success.

Workspace Studio API quotas

Traffic sent to the workspacestudio.googleapis.com service is restricted to prevent system overload, encourage fair use of resources, and protect overall Google Workspace performance.

The following quotas are enforced:

Quota type Quota
Per minute per project 1,000 starter requests
Per minute per user 100 starter requests

The quota types are:

  • Per minute per project: Limits the cumulative number of starter events fired from a single developer's Google Cloud project to 1,000 requests per minute across all users running its starters.
  • Per minute per user: Limits any single end user's cumulative starter invocations in a given Cloud project to 100 requests per minute.

Handle time-based quota errors

If you exceed these quotas, the API returns an HTTP 429 Too Many Requests (or 429 Resource Exhausted) error code indicating that the rate quota has been exceeded.

To resolve these errors, your code should catch the exception and use a truncated exponential backoff strategy. Exponential backoff retries failed requests using progressively longer delays between attempts, including randomised jitter (recalculating a randomized delay on each iteration) to prevent multiple clients from synchronizing and retrying at the same time:

  1. Make a request to the Workspace Studio API.
  2. If the request fails with a 429 error, wait 1 second + random_number_milliseconds and retry.
  3. If it fails again, wait 2 seconds + random_number_milliseconds and retry.
  4. If it fails again, wait 4 seconds + random_number_milliseconds and retry.
  5. Continue this loop, doubling the delay up to a maximum_backoff threshold (typically 32 or 64 seconds).
  6. Once you reach the maximum backoff duration, retry using that constant delay until the maximum retry limit is reached, then halt and log the error.

Best practices

When designing and implementing a starter, consider the following best practices:

Emit single events instead of batch lists

Design your starter to emit an individual event for each distinct occurrence (such as a single record updated, a new message posted, or a task assigned) rather than emitting a single event containing a batch or list of items:

  • Consistency with built-in starters: In Workspace Studio, built-in Google Workspace starters (such as receiving an email in Gmail or a user joining a space in Google Chat) trigger on a single event. Emitting single-item events aligns with this behavior and provides a consistent, predictable experience for users across all starters.
  • Simpler flow configuration: Downstream steps in a flow typically process one item at a time. Emitting single-item events lets users map variables directly without adding complex steps to iterate over arrays or parse lists.
  • Handle polling and batch changes individually: If your backend service polls an external API and detects multiple changed items during a single polling interval, fire an individual starter event for each item rather than bundling them into one batch event.
  • Manage event rate and quotas: Because firing individual events for multiple changed items can cause a sudden burst of requests, ensure your service stays within Workspace Studio API quotas (such as the 100 requests per minute per user limit). If a polling cycle yields a large volume of items (for example, more than 100 changed records), pace or throttle event dispatches over time to avoid 429 Too Many Requests errors.

Core behaviors and edge cases

When integrating starters, developers must handle specific error behaviors and runtime features:

  • No test run support: Workspace Studio doesn't support test runs for starters.
  • Idempotency and replay prevention: Although not strictly required, you should include a unique requestId (such as a UUID) in your HTTP or Apps Script payload. Providing a requestId ensures idempotency by allowing the API to detect and ignore duplicate notifications, preventing the flow from running multiple times for a single event.
  • Disabled and re-enabled flows: When a flow containing your starter is disabled in Workspace Studio, Google sends a triggerDeletion lifecycle event to your onManageFunction callback. In addition, any calls to the associated FireTrigger method return a 404 Not Found error return code (Requested entity was not found.). Your service should react to 404 errors by stopping future event notification deliveries for that starter instance ID.

    If a user later re-enables the flow, Google initiates a new subscription lifecycle by invoking your onManageFunction callback with a new triggerCreation event containing a new triggerId and notifyUri. The previous triggerId is permanently decommissioned and isn't reactivated, so your service shouldn't poll or check whether an old trigger instance has been re-enabled. For more information, see Handle the starter subscription lifecycle.

  • Idempotent subscription deletion: Your onManageFunction callback function must handle starter deletion requests from Google idempotently. If Google calls the deletion hook multiple times for the same triggerId (for example, during retries due to temporary connection losses), the function should return successfully.

  • Flow quotas: Beyond the Workspace Studio API quotas, user flows are subject to additional internal quota controls. High-frequency loops or excessive event volume may exceed safety thresholds, resulting in the automatic disablement of the flow.