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'snotifyUrior thetriggers.fireAPI 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 astriggers/{triggerId}.outputs(map, optional): A map of starter output variables representing the event data. Each value is aVariableDataobject supporting typed lists (such as,stringValues,booleanValues,integerValues).log(object, optional): ATextFormatmarkup 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:
- Make a request to the Workspace Studio API.
- If the request fails with a
429error, wait1 second + random_number_millisecondsand retry. - If it fails again, wait
2 seconds + random_number_millisecondsand retry. - If it fails again, wait
4 seconds + random_number_millisecondsand retry. - Continue this loop, doubling the delay up to a
maximum_backoffthreshold (typically 32 or 64 seconds). - 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 Requestserrors.
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 arequestIdensures 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
triggerDeletionlifecycle event to youronManageFunctioncallback. In addition, any calls to the associatedFireTriggermethod return a404 Not Founderror return code (Requested entity was not found.). Your service should react to404errors 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
onManageFunctioncallback with a newtriggerCreationevent containing a newtriggerIdandnotifyUri. The previoustriggerIdis 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
onManageFunctioncallback function must handle starter deletion requests from Google idempotently. If Google calls the deletion hook multiple times for the sametriggerId(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.
Related topics
- Build a step
- Connect your Google Workspace add-on to a third-party service
- Input variables
- Output variables
- Log activity and errors
- Handle errors
- Workspace Studio event objects