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](https://developers.google.com/workspace/add-ons/studio/build-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

- A Google Account with access to Workspace Studio.
- A [Google Cloud project](https://developers.google.com/workspace/guides/create-project).

## Enable the API

In your Google Cloud project, enable the Google Workspace Studio API:

[Enable the API](https://console.cloud.google.com/flows/enableapi?apiid=workspacestudio.googleapis.com)

### Switch to a standard Google Cloud project (Google Apps Script only)

By default, Apps Script projects use a default
Cloud project that doesn't support enabling APIs or configuring OAuth
consent screens. If you're building your starter with
Apps Script, you must switch to a standard Google Cloud project:

1. In the Apps Script editor, click **Project Settings** .
2. Under **Google Cloud Platform (GCP) Project** , click **Change project**.
3. Enter your standard Cloud project number and click **Set project**.
4. After switching projects, re-authorize your script by running any function in the editor and granting the requested permissions.

For more information, see
[Switch to a standard Google Cloud project](https://developers.google.com/apps-script/guides/cloud-platform-projects#use_a_different_standard_project).

## Configure the OAuth consent screen

To communicate with the Workspace Studio API endpoint, your app or
service must authenticate using OAuth 2.0. Configure your project's OAuth
consent screen and add the following dedicated OAuth scope:

`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.

For more information, see [Configure the OAuth consent screen and choose
scopes](https://developers.google.com/workspace/guides/configure-oauth-consent).

### 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](https://developers.google.com/workspace/add-ons/guides/connect-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. You can retrieve an OAuth access token using
  `ScriptApp.getOAuthToken()`.

  > [!NOTE]
  > **Note:** When testing starters with Apps Script Web Apps, configure `"executeAs": "USER_ACCESSING"` in your manifest. If deployed as `"USER_DEPLOYING"` ("Me"), Google's security model drops non-core scopes like `workspace.studio.trigger` from `ScriptApp.getOAuthToken()`. In production environments accessible to all users, use a service account or an OAuth 2.0 refresh token.

## 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](https://developers.google.com/workspace/add-ons/concepts/workspace-manifests).

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",
      "oauthScopes": [
        "https://www.googleapis.com/auth/script.external_request",
        "https://www.googleapis.com/auth/workspace.studio.trigger"
      ],
      "urlFetchWhitelist": [
        "https://workspacestudio.googleapis.com/"
      ],
      "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`).
    If `notifyUri` is omitted from the event payload, construct the endpoint
    URL directly using the `triggerId`:
    `https://workspacestudio.googleapis.com/v1/triggers/{triggerId}: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](https://developers.google.com/workspace/add-ons/guides/alternate-runtimes).

## 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;
        // Fall back to constructing the endpoint URL if notifyUri is omitted.
        const notifyUri = triggerCreation.notifyUri ||
            ("https://workspacestudio.googleapis.com/v1/triggers/" +
             triggerId + ":fire");
        const inputs = triggerCreation.inputs;

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

        // Tip: In Apps Script, use PropertiesService to persist triggerId and notifyUri:
        // const props = PropertiesService.getUserProperties();
        // props.setProperty("notifyUri", notifyUri);
        // props.setProperty("triggerId", triggerId);
        //
        // 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;

        // In Apps Script, clean up stored properties:
        // PropertiesService.getUserProperties().deleteProperty("notifyUri");
        // PropertiesService.getUserProperties().deleteProperty("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.
     *     In Apps Script, defaults to ScriptApp.getOAuthToken().
     */
    function simulateEventFire(notifyUri, triggerId, userAccessToken) {
      // In Apps Script, obtain the OAuth token using ScriptApp.getOAuthToken().
      const token = userAccessToken || ScriptApp.getOAuthToken();
      // 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 " + token
        },
        "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`](https://developers.google.com/workspace/add-ons/studio/reference/rest/v1/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](https://developers.google.com/workspace/add-ons/studio/build-a-starter#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.

> [!NOTE]
> **Note:** Custom batch starters that emit lists of objects are technically supported, but they're inconsistent with standard starters and might require extra logic in downstream steps to handle arrays.

## 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](https://developers.google.com/workspace/add-ons/studio/build-a-starter#handle-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.

- **Permission denied (403) on trigger fire** : If calls to the
  `triggers.fire` endpoint return `403 PERMISSION_DENIED` with a
  `SERVICE_DISABLED` error status, verify that:

  - The Google Workspace Studio API is enabled in your Google Cloud project.
  - If using Google Apps Script, your project is linked to a standard Cloud project rather than the default Apps Script Cloud project.
  - The OAuth access token includes the `https://www.googleapis.com/auth/workspace.studio.trigger` scope.

## Related topics

- [Build a step](https://developers.google.com/workspace/add-ons/studio/build-a-step)
- [Connect your Google Workspace add-on to a third-party service](https://developers.google.com/workspace/add-ons/guides/connect-third-party-service)
- [Input variables](https://developers.google.com/workspace/add-ons/studio/input-variables)
- [Output variables](https://developers.google.com/workspace/add-ons/studio/output-variables)
- [Log activity and errors](https://developers.google.com/workspace/add-ons/studio/activity-logs)
- [Handle errors](https://developers.google.com/workspace/add-ons/studio/handle-errors)
- [Workspace Studio event objects](https://developers.google.com/workspace/add-ons/studio/event-objects)
- [Codelab: Build custom Workspace Studio starter and action
  steps](https://codelabs.developers.google.com/workspace-studio-custom-extensions)
- [Codelab: Build custom Workspace Studio starter and action
  steps with
  Node.js](https://codelabs.developers.google.com/workspace-studio-custom-extensions-nodejs)