# GoToWebinar

- Tool: FlutterFlow
- Difficulty: Intermediate
- Time required: 45 minutes
- Last updated: July 2026

## TL;DR

Connect FlutterFlow to GoToWebinar using the GoTo REST API v2 with OAuth 2.0 authorization-code flow. Because GoToWebinar requires a client secret and a long-lived refresh token, the entire auth exchange must live in a Firebase Cloud Function — never in compiled Dart. FlutterFlow API Calls then hit your function to list webinars, register attendees, and surface the joinUrl in-app.

## Connecting FlutterFlow to GoToWebinar: OAuth-Backed Webinar Data in Your App

GoToWebinar is a popular platform for hosting online events, and its REST API makes it possible to embed webinar discovery, registration, and attendance data inside a FlutterFlow mobile or web app. The two most common use-cases are a 'Register for our upcoming webinar' screen — where the app shows a list of upcoming webinars and lets users submit a registration form — and an events dashboard for organizers showing past attendance and engagement stats.

The integration wrinkle is GoTo's authentication model. GoToWebinar uses OAuth 2.0 with the authorization-code grant, which requires a client secret and produces a refresh token that must be stored and protected. Because FlutterFlow compiles to client-side Flutter/Dart, any credential placed in a Dart Custom Action or API Call header ships inside the compiled app binary, where it can be extracted. The client secret and refresh token must therefore live in a Firebase Cloud Function — the function exchanges the refresh token for a fresh access token and proxies all GoToWebinar API calls, returning only the data FlutterFlow needs.

GoToWebinar pricing includes Lite (~$49/month), Standard (~$99/month), and Pro/Enterprise tiers — each plan controls how many attendees your webinars support. Verify current pricing at goto.com/webinar. The GoTo Developer app registration is free. Access token lifetimes and rate limits are per-app/account; the Cloud Function must handle token refresh and 429 backoff gracefully.

## Before you start

- A FlutterFlow project open in the browser at app.flutterflow.io
- A GoToWebinar organizer account (Lite plan or higher)
- A GoTo Developer account at developer.goto.com to register an OAuth app and get the client ID and secret
- A Firebase project on the Blaze plan (required for Cloud Functions with outbound HTTP)
- Your GoTo organizer key (visible in your GoTo admin panel — needed for the API endpoints)

## Step-by-step guide

### 1. Step 1: Register a GoTo Developer app and capture the refresh token

Before writing any FlutterFlow code, you need OAuth 2.0 credentials from GoTo's developer portal. Open developer.goto.com and sign in with your GoToWebinar organizer account. Create a new application, fill in a name and a redirect URI (you can use a localhost URL for this one-time setup step, such as http://localhost:3000/callback), and select the GoToWebinar product scope. After creating the app, copy the Client ID and Client Secret.

The authorization-code flow requires a one-time browser visit by the GoToWebinar organizer to grant permission and obtain an authorization code. Construct the authorization URL manually and open it in your browser:
https://authentication.logmeininc.com/oauth/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REDIRECT_URI

After the organizer clicks Authorize, GoTo redirects to your redirect URI with a ?code= parameter in the URL. Copy that code immediately — it expires within a few minutes.

Next, exchange the code for a refresh token. You can do this with a one-time curl call or a short Node.js script. Send a POST request to https://authentication.logmeininc.com/oauth/token with:
- grant_type: authorization_code
- code: the code from the redirect
- client_id and client_secret
- redirect_uri: the same URI used above

The response includes an access_token and a refresh_token. Copy the refresh_token — this is the long-lived credential you will store in your Firebase Cloud Function. Never put it in FlutterFlow.

```
// One-time refresh token exchange (run locally, not in FlutterFlow)
const axios = require('axios');
const params = new URLSearchParams({
  grant_type: 'authorization_code',
  code: 'YOUR_AUTH_CODE',
  client_id: 'YOUR_CLIENT_ID',
  client_secret: 'YOUR_CLIENT_SECRET',
  redirect_uri: 'http://localhost:3000/callback'
});
const res = await axios.post(
  'https://authentication.logmeininc.com/oauth/token',
  params.toString(),
  { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
console.log('Refresh token:', res.data.refresh_token);
```

**Expected result:** You have a refresh token saved securely. You also have your GoToWebinar organizer key (visible in the GoTo admin panel) ready for use in API endpoint paths.

### 2. Step 2: Deploy a Firebase Cloud Function that holds the refresh token and proxies GoToWebinar calls

The Cloud Function is the backbone of this integration. It stores the client secret and refresh token in Firebase environment config (never in source code), mints fresh access tokens by calling the GoTo token endpoint, and proxies the GoToWebinar API calls that FlutterFlow triggers.

In your Firebase project (Blaze plan), create a Cloud Functions project. The function below handles three routes:
- GET /webinars: fetches upcoming webinars for your organizer key
- POST /register: creates a registrant for a given webinarKey
- GET /attendees: fetches attendees for a past webinar

Before deploying, set your Firebase environment config:
firebase functions:config:set goto.client_id="YOUR_ID" goto.client_secret="YOUR_SECRET" goto.refresh_token="YOUR_REFRESH_TOKEN" goto.organizer_key="YOUR_KEY"

Then deploy with firebase deploy --only functions. The token refresh logic calls https://authentication.logmeininc.com/oauth/token with grant_type: refresh_token and caches the access token in memory until it expires. On any 401 from the GoToWebinar API, the function clears the cache and re-fetches.

```
const functions = require('firebase-functions');
const axios = require('axios');

let cachedToken = null;
let tokenExpiry = 0;

async function getAccessToken(cfg) {
  if (cachedToken && Date.now() < tokenExpiry) return cachedToken;
  const params = new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: cfg.refresh_token,
    client_id: cfg.client_id,
    client_secret: cfg.client_secret
  });
  const res = await axios.post(
    'https://authentication.logmeininc.com/oauth/token',
    params.toString(),
    { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
  );
  cachedToken = res.data.access_token;
  tokenExpiry = Date.now() + (res.data.expires_in - 60) * 1000;
  return cachedToken;
}

exports.gotowebinar = functions.https.onRequest(async (req, res) => {
  const cfg = functions.config().goto;
  const BASE = 'https://api.getgo.com/G2W/rest/v2';
  try {
    const token = await getAccessToken(cfg);
    const headers = { Authorization: `Bearer ${token}` };
    if (req.method === 'GET' && req.path === '/webinars') {
      const r = await axios.get(
        `${BASE}/organizers/${cfg.organizer_key}/webinars`,
        { headers }
      );
      return res.json(r.data);
    }
    if (req.method === 'POST' && req.path === '/register') {
      const { webinarKey, firstName, lastName, email } = req.body;
      const r = await axios.post(
        `${BASE}/webinars/${webinarKey}/registrants`,
        { firstName, lastName, email },
        { headers: { ...headers, 'Content-Type': 'application/json' } }
      );
      return res.json(r.data);
    }
    res.status(404).json({ error: 'Unknown route' });
  } catch (err) {
    const status = err.response ? err.response.status : 500;
    res.status(status).json({ error: err.message });
  }
});
```

**Expected result:** The Firebase Cloud Function deploys successfully. Calling GET /webinars from a REST client (e.g. Postman) returns a JSON array of your upcoming GoToWebinar sessions. Calling POST /register with a test email returns a joinUrl.

### 3. Step 3: Create a GoToWebinar API Group and API Calls in FlutterFlow

With the Cloud Function running and tested, you can now configure FlutterFlow to call it.

In FlutterFlow, click API Calls in the left navigation panel (under Integrations in the left nav). Click + Add and choose Create API Group. Name it GoToWebinar and set the Base URL to your Firebase Cloud Function's HTTPS trigger URL — for example, https://us-central1-your-project.cloudfunctions.net/gotowebinar. If you deployed the function with a single entry point that handles multiple routes, the base URL is the function trigger URL and each API Call will append the route path.

Add the first API Call: click Add API Call inside the group, name it GetWebinars, set the Method to GET and the Endpoint to /webinars. This call needs no variables for the basic case — the Cloud Function reads the organizer key from its own config.

Add the second API Call: name it RegisterAttendee, set the Method to POST and the Endpoint to /register. Under the Variables tab, add three variables: webinarKey (String), firstName (String), lastName (String), and email (String). Under the Body tab, select JSON and construct the body using the FlutterFlow variable syntax:

```json
{
  "webinarKey": "{{ webinarKey }}",
  "firstName": "{{ firstName }}",
  "lastName": "{{ lastName }}",
  "email": "{{ email }}"
}
```

For each API Call, click the Response & Test tab and paste a sample response JSON. For GetWebinars, paste an example from your Cloud Function test (a JSON object with a webinars array). Click Generate JSON Paths to create bindable path variables like $.webinars[*].subject, $.webinars[*].times[0].startTime, and $.webinars[*].webinarKey. For RegisterAttendee, add a JSON path for $.joinUrl.

Save both API Calls.

```
{
  "GetWebinars": {
    "method": "GET",
    "endpoint": "/webinars",
    "headers": {},
    "variables": []
  },
  "RegisterAttendee": {
    "method": "POST",
    "endpoint": "/register",
    "headers": { "Content-Type": "application/json" },
    "body": {
      "webinarKey": "{{ webinarKey }}",
      "firstName": "{{ firstName }}",
      "lastName": "{{ lastName }}",
      "email": "{{ email }}"
    }
  }
}
```

**Expected result:** The GoToWebinar API Group appears in the API Calls panel with two calls: GetWebinars and RegisterAttendee. The Response & Test tab for GetWebinars shows JSON Paths for webinar fields. The RegisterAttendee call shows a joinUrl JSON Path.

### 4. Step 4: Bind the webinar list to a ListView and wire the registration action

Now that the API Calls are configured, you can connect them to your FlutterFlow UI.

On your Upcoming Webinars screen, add a ListView widget. Select the ListView and open the Backend/API Queries panel (right panel → Backend Query). Choose API Call and select GoToWebinar → GetWebinars. Enable the setting to trigger the query on page load. After the query runs, FlutterFlow populates the ListView with one item per webinar.

Inside the ListView's item template, add widgets to display each webinar: a Text widget bound to the $.webinars[*].subject JSON Path for the webinar title, and another Text widget bound to $.webinars[*].times[0].startTime for the date. Add a Button labeled Register.

For the Register button, open the Action Flow Editor. Click + Add Action → Backend/API Calls → GoToWebinar → RegisterAttendee. Map the webinarKey argument to the current list item's $.webinars[*].webinarKey value. Map firstName, lastName, and email to your logged-in user's profile fields (from Firestore, Supabase, or your authentication provider).

After the RegisterAttendee action, add a second action: + Add Action → Custom Actions (if you've added url_launcher) or the built-in Open URL action. Pass the joinUrl from the RegisterAttendee API response (use the Action Output variable for the joinUrl JSON Path) to open the webinar join link. Add a conditional action before the Open URL step to show a success snack bar if the response contained a joinUrl, or an error message if the registrant endpoint returned an error status.

For debouncing, disable the Register button after the first tap using a page variable (isRegistering: bool) to prevent double-registration, and re-enable it only after the action completes.

**Expected result:** The Upcoming Webinars screen loads a scrollable list of GoToWebinar sessions on page open. Tapping Register on a webinar card submits the user's details and opens the GoToWebinar join URL in the browser or native app.

### 5. Step 5: Handle errors and test on a real build

Before shipping, verify the integration handles the most common failure modes gracefully.

In your RegisterAttendee action sequence, add an If/Else branch after the API Call to check the response status. If the HTTP status code is not 201 (Created), route to an error path that shows a bottom sheet or snackbar with a human-readable message. Common error codes from the GoToWebinar API include 400 (missing required registrant field), 401 (expired or invalid access token — the Cloud Function should handle this, but surface it if it bubbles up), 404 (webinarKey not found), and 429 (rate limit exceeded).

For the webinar list, add an empty state widget inside the ListView for when GetWebinars returns an empty webinars array — this happens when no webinars are scheduled. Also add a loading indicator (CircularProgressIndicator or a Shimmer) while the Backend Query is in the loading state.

Test the integration by running a real web or device build — not the FlutterFlow canvas preview. Backend Queries and API Calls work in canvas mode, but Custom Actions (like a url_launcher Open URL action) require a real build. Click the Test button in the API Calls panel to verify your Cloud Function returns data before wiring the UI. If the Test tab returns a 401, the Cloud Function's refresh token may have expired or been revoked — re-run the authorization-code flow to get a new one.

For production readiness, add a short header to your Cloud Function that checks a shared secret token passed from FlutterFlow (store it in an App State variable or as a custom header on the API Call) to prevent unauthorized calls to your function endpoint.

**Expected result:** The app handles empty webinar lists, registration errors, and loading states gracefully. A real web or device build successfully lists webinars and completes a registration that delivers a joinUrl.

## Best practices

- Never put the GoTo client secret or refresh token in a FlutterFlow API Call, App Values, or Dart Custom Action — always hold them in Firebase Cloud Functions environment config
- Cache the GoTo access token in the Cloud Function with a buffer before expiry (subtract 60 seconds from expires_in) to avoid fetching a new token on every request
- Debounce the Register button and disable it immediately on tap to prevent duplicate registrant submissions
- Parse the joinUrl from the RegisterAttendee response and store it in the user's Firestore document so they can access it later without re-registering
- Add CORS headers to your Cloud Function responses for web build compatibility — mobile builds don't need them but web builds do
- Test the GetWebinars call in the API Calls Test tab before wiring UI — if the Test tab fails, there is no point debugging the FlutterFlow UI
- Handle the empty-webinars state explicitly in the ListView with an empty state widget — users frequently open the app when no webinars are scheduled
- Store your organizer key in Firebase environment config alongside the refresh token so the function stays configurable without code changes

## Use cases

### Events app that lists and registers users for upcoming webinars

A community or SaaS app shows a scrollable list of the organizer's upcoming GoToWebinar sessions. Each webinar card displays the title, date, and a 'Register' button. Tapping Register calls a Cloud Function that POST-s to the GoToWebinar registrants endpoint and returns a joinUrl, which the app opens with url_launcher.

Prompt example:

```
Build an 'Upcoming Webinars' screen that fetches our GoToWebinar sessions from the backend and shows a card for each one. Add a 'Register' button on each card that submits the logged-in user's name and email as a registrant and then opens the join link.
```

### Organizer dashboard showing attendance analytics

An internal FlutterFlow app for webinar organizers pulls attendance data from past sessions using the GoToWebinar attendees and sessions endpoints. It displays attendance counts and duration averages in a summary card, helping organizers evaluate event performance without leaving the app.

Prompt example:

```
Create an organizer dashboard that pulls the last 5 webinars from my GoToWebinar account, shows attendee counts for each, and highlights the session with the highest attendance rate.
```

### E-learning platform with embedded webinar registration

An e-learning app integrates GoToWebinar as its live-session layer. Learners browse course modules and, when a live webinar is scheduled, see a 'Join Live Session' card that registers them in the background and reveals the GoToWebinar join link directly in the app.

Prompt example:

```
On the course detail screen, add a 'Join Live Session' section that automatically registers the logged-in learner for the associated GoToWebinar and shows them a 'Join Now' button that opens the webinar link.
```

## Troubleshooting

### GoToWebinar API returns 401 Unauthorized from the Cloud Function

Cause: The access token expired (token TTL varies by GoTo plan) or the refresh token was revoked (this happens if the GoTo Developer app is disconnected or the organizer revokes access in their GoTo account).

Solution: In your Cloud Function, clear the cached token on any 401 response from api.getgo.com and retry the token refresh from authentication.logmeininc.com/oauth/token with grant_type: refresh_token. If the refresh itself returns 400 invalid_grant, the refresh token has been revoked and you must run the authorization-code flow again to get a new one.

```
// On 401 from GoTo API:
cachedToken = null;
tokenExpiry = 0;
const freshToken = await getAccessToken(cfg); // retries refresh
```

### XMLHttpRequest error on webinar list in FlutterFlow web build

Cause: The Cloud Function is not returning CORS headers, so the browser blocks the response in web builds (mobile builds are unaffected).

Solution: Add CORS headers to your Firebase Cloud Function response. At the top of the handler, add res.setHeader('Access-Control-Allow-Origin', '*') and handle the OPTIONS preflight request by returning 204. Alternatively, use the 'cors' npm package to wrap the function handler.

```
// Add to top of Cloud Function handler:
res.setHeader('Access-Control-Allow-Origin', '*');
if (req.method === 'OPTIONS') { return res.status(204).send(''); }
```

### Registration returns 400 Bad Request from GoToWebinar

Cause: The registrant POST body is missing a required field. GoToWebinar requires at minimum firstName, lastName, and email — all three must be present and non-empty.

Solution: Verify all three fields are mapped in the FlutterFlow action and are not empty. Check that the user's profile data (from Firestore or your auth provider) is fully populated before the action fires. Add a validation step in the Action Flow Editor before calling RegisterAttendee.

### API Calls Test tab succeeds but the FlutterFlow canvas Backend Query returns no data

Cause: The Test tab runs from FlutterFlow's servers and succeeds, but canvas-mode Backend Queries use the same network context — this is typically a JSON Path mismatch rather than a true network error.

Solution: Re-open the Response & Test tab for the GetWebinars call, paste the actual response JSON from your Cloud Function, and click Generate JSON Paths again. Verify that the JSON Paths match the exact structure your Cloud Function returns — if you changed the function's response shape, regenerate the paths.

## Frequently asked questions

### Can I complete the GoTo OAuth 2.0 flow inside FlutterFlow without a Cloud Function?

No. The authorization-code flow requires exchanging a client secret for a refresh token, and storing that refresh token securely. FlutterFlow compiles to client-side Dart — any secret stored in the app is embedded in the binary and can be extracted. The authorization-code exchange and refresh token storage must happen in a Firebase Cloud Function that operates server-side.

### How do I get my GoToWebinar organizer key?

The organizer key is visible in your GoTo admin panel under your account profile, and it is also returned in the token exchange response as part of the account_info or scope payload. Check the GoTo Developer documentation for the exact field name for your plan. You will use it as the {organizerKey} path parameter in API endpoints like GET /organizers/{organizerKey}/webinars.

### Will the refresh token ever expire?

GoTo refresh tokens can be revoked if you disconnect the OAuth app in your GoTo admin settings, if the token sits unused for an extended period (verify the exact idle-expiry policy in GoTo's developer docs), or if you explicitly call the token revocation endpoint. If the function returns 400 invalid_grant on a token refresh, you need to re-run the authorization-code flow to get a new refresh token.

### Can FlutterFlow receive GoToWebinar webhook events (registrations, attendance updates)?

Not directly. GoToWebinar can send webhook events to a registered HTTPS endpoint, but FlutterFlow has no public webhook endpoint — it is a compiled client app. Register a Firebase Cloud Function URL as your GoToWebinar webhook endpoint; the function writes event data to Firestore, which FlutterFlow reads via a real-time Backend Query.

### How do I show the joinUrl to a user who already registered?

After a successful registration, store the returned joinUrl in the user's Firestore document alongside the webinarKey. On subsequent app opens, query Firestore for the user's registrations and show 'Join Webinar' buttons that open the stored joinUrl directly — this avoids re-calling the GoToWebinar API and hitting rate limits or duplicate-registration errors.

---

Source: https://www.rapidevelopers.com/flutterflow-integrations/gotowebinar
© RapidDev — https://www.rapidevelopers.com/flutterflow-integrations/gotowebinar
