Skip to main content

Command Palette

Search for a command to run...

CF7 to Google Sheets: Diagnosing "Invalid OAuth2 Access Token" From the Stack Trace Up

Updated
9 min read
CF7 to Google Sheets: Diagnosing "Invalid OAuth2 Access Token" From the Stack Trace Up
R
Experienced software developer skilled in solving real-world business and technical challenges through WordPress, custom web development, API integrations, and scalable software solutions. Passionate about building efficient, user-friendly digital products.

A developer posted this error on the WordPress support forums after their CF7 Google Sheets Connector stopped writing form submissions:

[ERROR_MSG] => Auth, Invalid OAuth2 access token
[TRACE_STK] => #0 class-gs-service.php(182): CF7GSC_googlesheet->auth()
               #1 class-wp-hook.php(308): Gs_Connector_Service->cf7_save_to_google_sheets()
               #4 submission.php(119): do_action('wpcf7_mail_sent', ...)

The plugin author replied that it was probably a Google library conflict with another plugin and suggested deactivating everything else.

That is one of three possible causes. The stack trace contains enough information to narrow down which one you are dealing with before you start deactivating plugins. This post explains how to read the trace, what each cause looks like, and how to fix each one including the commonly missed 7-day OAuth token expiry that catches most developers.

Reading the Stack Trace Bottom-Up

Stack traces are printed with the most recent call first. Reading this one from the bottom (oldest call) to the top (where the error occurred) gives you the execution path:

#22 index.php         wp()
#21 class-wp.php      WP->main()
#20 class-wp.php      WP->parse_request()
#19 class-wp.php      WP->parse_request() [internal]
#15 class-wp-hook.php rest_api_loaded()
#14 rest-api.php      WP_REST_Server->serve_request()
#13 rest-api.php      WP_REST_Server->dispatch()
#12 rest-api.php      WP_REST_Server->respond_to_request()
#10 rest-api.php      WPCF7_ContactForm->submit()       <- CF7 processes submission
#8  submission.php    WPCF7_Submission->proceed()
#5  submission.php    WPCF7_Submission->{closure}()
#4  submission.php    do_action('wpcf7_mail_sent', ...)  <- hook fires AFTER email sent
#1  class-wp-hook.php Gs_Connector_Service->cf7_save_to_google_sheets() <- plugin runs
#0  class-gs-service.php CF7GSC_googlesheet->auth()      <- auth fails HERE

Three things this tells you immediately:

1. The CF7 form submission succeeded. The error fires at wpcf7_mail_sent (frame 4), which only fires after CF7 has processed the submission and sent the email. The form works. Only the Sheets write failed.

2. The integration hooks into wpcf7_mail_sent, not wpcf7_before_send_mail. This means the Sheets write happens after mail delivery, so a Sheets failure does not show a form error to the user. It fails silently unless you are watching PHP error logs.

3. The failure is in auth() at line 182 of class-gs-service.php. This method calls Google's OAuth validation to confirm the stored token is still valid. Google's response is "it is not." The question is why.

The Three Causes of Invalid OAuth2 Access Token

Cause 1: OAuth Refresh Token Expired Due to Google's 7-Day Testing Limit

This is the most common cause and the one the forum reply did not mention.

Google's OAuth 2.0 implementation for web applications issues two tokens:

  • Access token: Short-lived, expires in 3600 seconds (1 hour). Used in the Authorization: Bearer header for API calls.

  • Refresh token: Long-lived, used to request new access tokens without user re-authorization.

GSheetConnector stores both when you first authorize it. It should automatically use the refresh token to get a new access token when the stored one expires. This works correctly unless the refresh token itself has been invalidated.

Google invalidates refresh tokens under these conditions:

Condition Detail
OAuth app in Testing status Refresh tokens expire after 7 days for all users
Google account password changed All tokens for the account are revoked immediately
User revoked app access Google Account > Security > Third-party app access > revoke
App inactive for 6 months Google revokes tokens for dormant apps
50-token limit exceeded Google revokes the oldest token when the 51st is issued for the same user+app pair

The 7-day Testing limit is the cause in most developer setups. When you first set up GSheetConnector, you create or use a Google Cloud project. If the OAuth consent screen is in Testing status (the default for new projects), every refresh token expires after 7 days. The plugin works for a week, then breaks. The next form submission produces this exact error.

Confirming this is your cause:

Go to Google Cloud Console > APIs and Services > OAuth consent screen. Check the Publishing status field. If it shows Testing, this is your cause.

Fixing it:

Change the Publishing status to In production. You do not need Google's verification for internal tools used by your own Google account. Verification is only required if you want external users (people outside your organization) to authorize your app. Changing to production gives refresh tokens indefinite lifetime (subject to the other conditions above).

After changing to production, go to GSheetConnector's settings and reconnect: revoke the current authorization and re-authorize. The new refresh token will not have the 7-day expiry.

Cause 2: Google Client Library Version Conflict

This is what the plugin author identified. GSheetConnector bundles Google's PHP client library. Several other plugins do the same:

  • Google Analytics integration plugins

  • Google Ads conversion tracking plugins

  • WooCommerce Google integrations

  • Google Tag Manager plugins

  • Any plugin using google/apiclient

When two plugins bundle different versions of Google_Client and both activate, PHP loads whichever class definition it encounters first. The second plugin's version is silently ignored because PHP cannot redefine an already-loaded class. Depending on which version runs, auth() may use outdated token validation logic and reject valid tokens.

Diagnosing which plugin is causing the conflict:

// Add temporarily to functions.php — remove after diagnosis
add_action('plugins_loaded', function() {
    if (class_exists('Google_Client')) {
        $r = new ReflectionClass('Google_Client');
        error_log('[Google Library] Google_Client loaded from: ' . $r->getFileName());
        error_log('[Google Library] Version constant: ' . (defined('Google\Client::LIBVER')
            ? \Google\Client::LIBVER
            : 'pre-namespace version'));
    }
    if (class_exists('Google\Client')) {
        $r = new ReflectionClass('Google\Client');
        error_log('[Google Library] Namespaced Google\Client from: ' . $r->getFileName());
    }
}, 999);

Check wp-content/debug.log after loading any admin page. If the path points to a directory other than GSheetConnector's, another plugin loaded its version first.

You can also run a quick audit from the command line:

# Find all google/apiclient installations across plugins
find /path/to/wp-content/plugins -name "Google_Client.php" -o -name "Client.php" \
  | xargs grep -l "class Google_Client\|class Client" 2>/dev/null \
  | grep -i google

Fixing it:

Option A: Deactivate conflicting Google-related plugins one at a time until the error stops.

Option B: Use a plugin that avoids the shared library problem entirely. Contact Form to API calls the Google Sheets API via a direct HTTP request using wp_remote_post, with no dependency on the bundled Google PHP library. It does not load Google_Client at all, so there is no version conflict possible.

Cause 3: Revoked Authorization or Wrong Account

If the Google account that authorized GSheetConnector has had its access revoked, the stored tokens are permanently invalid regardless of expiry timelines.

Scenarios that cause this:

  • A team member's Google account was suspended or deleted after they set up the integration

  • A Google Workspace admin ran an org-wide third-party app revocation

  • The target Google Sheet was transferred to a different Drive account

  • Someone manually revoked access at Google Account > Security > Third-party app access

Confirming this is your cause:

Visit myaccount.google.com/connections. Find GSheetConnector in the list of connected apps. If it is not listed, the authorization has been revoked.

Fixing it:

Go to GSheetConnector's settings, revoke the current authorization, and re-authorize using the correct Google account. Make sure that account has at least Editor access to the target spreadsheet.

Validating Token State Directly

Before reconnecting the plugin, confirm the stored token is actually invalid:

# Test access token validity (replace with the actual token from plugin settings)
curl "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=YOUR_ACCESS_TOKEN"

Valid token response:

{
  "issued_to": "your-client-id.apps.googleusercontent.com",
  "scope": "https://www.googleapis.com/auth/spreadsheets",
  "expires_in": 3412,
  "access_type": "online"
}

Expired or revoked token response:

{
  "error": "invalid_token",
  "error_description": "Token has been expired or revoked."
}

If you get invalid_token, the token stored in the plugin is genuinely invalid. The fix is determined by which of the three causes above applies.

Architectural Alternative: Service Account Authentication

All three failure modes above exist because GSheetConnector uses OAuth 2.0 user authorization. This requires an active refresh token, which can be revoked, expired, or conflicted.

Google Sheets also supports Service Account authentication. A service account is a non-human Google account with a JSON key file for authentication. It does not have user-facing token expiry and cannot be revoked by a user accidentally.

The Google Sheets API endpoint for appending rows:

POST https://sheets.googleapis.com/v4/spreadsheets/{SPREADSHEET_ID}/values/{RANGE}:append
     ?valueInputOption=USER_ENTERED
Authorization: Bearer SERVICE_ACCOUNT_ACCESS_TOKEN
Content-Type: application/json

{
  "values": [
    ["Jane Smith", "jane@example.com", "+1234567890", "2024-01-15T10:30:00"]
  ]
}

Setting up service account authentication:

  1. In Google Cloud Console, go to IAM and Admin > Service Accounts

  2. Create a new service account

  3. Download the JSON key file

  4. Share the target Google Sheet with the service account's email address (it looks like name@project.iam.gserviceaccount.com) with Editor permissions

  5. Use the JSON key to generate access tokens programmatically

The service account access token is generated from the JSON key file using JWT signing, not from a user authorization flow. It does not expire in 7 days, cannot be accidentally revoked by a user, and has no library version conflict risk because token generation is self-contained.

For CF7 integration using this approach without writing PHP from scratch, Contact Form to API handles the outbound HTTP call to the Sheets API endpoint with the Bearer token you provide.

Ordered Diagnosis

Step 1: Check Google Cloud Console
        APIs and Services > OAuth consent screen
        Is Publishing status = "Testing"?
        YES -> Change to "In production", reconnect plugin
        NO  -> Continue

Step 2: Check token age
        Was the plugin connected more than 7 days ago?
        Even if status is Production, did the Google account password change?
        YES -> Reconnect the plugin
        NO  -> Continue

Step 3: Check for conflicting Google plugins
        Add ReflectionClass diagnostic to functions.php
        Does Google_Client load from a path outside GSheetConnector?
        YES -> Identify and deactivate conflicting plugin
        NO  -> Continue

Step 4: Check authorization status
        Visit myaccount.google.com/connections
        Is GSheetConnector listed?
        NO  -> Re-authorize from scratch using the correct account
        YES -> Revoke and re-authorize anyway to get a fresh token

Summary

Cause Distinguishing sign Fix
OAuth app in Testing status Works for 7 days then breaks Change to Production in Google Cloud Console, reconnect
Google library version conflict Error appears after activating another Google plugin Identify conflicting plugin via ReflectionClass, deactivate
Revoked or wrong account authorization Plugin not listed in Google account connections Re-authorize with correct account that has Sheet access

The stack trace tells you the error is in auth() during a wpcf7_mail_sent callback. The form submission itself succeeds. Only the Sheets write fails. Fix the token, and Sheets logging resumes without any changes to your CF7 forms.

More from this blog

R

Rahul Dev Labs

18 posts