The connection is a token with a lifetime
For your app to write to a calendar when the owner is not at the keyboard, it holds a refresh token obtained once, when the owner consented. Google's documentation says that token is returned only on the first authorisation, and only if offline access was requested. The app trades it for short-lived access tokens. When Google stops honouring the refresh token, the app gets an invalid_grant error and every later call fails until the owner consents again.
- Keep the refresh token in your secret store, never in the repository.
- Request the narrowest calendar permission that works.
- Record when the consent was given.
The documented reasons it stops
Google lists several: the user revoked access; the token went unused for six months; the account passed the cap of 100 refresh tokens per client ID, after which the oldest is invalidated without warning; a time-limited grant ended; or an administrator restricted the service. There is also a startling one for new projects: a Cloud project with an external user type in Testing status gets refresh tokens that last seven days, unless the only scopes are openid, email and profile. Calendar permissions are not in that exception.
- A seven-day failure on a new app often means Testing status, which the project owner can change.
- Test accounts that repeatedly reconnect can hit the per-client cap.
- Moving a project to production may bring Google's verification requirements for the scopes used; that review belongs to the account holder.
Design for the break
A booking must be saved whether or not the calendar is reachable. When the sync fails with invalid_grant, mark the connection as needing reconnection, show that state where staff will see it, and put each unsynced booking on a list. After the owner reconnects, one action sends the list. Do not retry an invalid_grant in a loop: retrying cannot fix a token Google no longer honours.
- Alert a named person when the state changes, not only in a log.
- Count missed bookings and show the oldest.
- Send each only once after reconnecting, using the stable event ID.
Exact redirect addresses and the consent screen
The redirect address in the authorisation request must match one registered for the client exactly, including scheme, letter case and any trailing slash, and must use HTTPS except for localhost. Google's policy page also says a production client should not carry redirect addresses that only your team can reach, such as developer test servers, which is why staging and production use separate clients.
- Keep staging and production OAuth clients apart.
- Record who owns the Google Cloud project and its consent screen.
- Do not share the client secret in an enquiry.
How the paid outcome is accepted
The Google Calendar outcome includes a revocation test: after access is revoked, a new booking is still saved, appears on the missed list and the app shows a reconnect state; after reconnecting, one action sends it once. The fixed £445 test price is untested and payment follows your sign-off. The monthly integration-health service watches for this class of lapse.
Sources and limits
- Google: OAuth 2.0 overview, refresh token expiration Checked 2026-10-11.
- Refresh tokens can stop working if the user revokes access, the token goes unused for six months, the account exceeds its token limit, a time-limited grant ends or an admin restricts the service.
- An external app in Testing status gets seven-day refresh tokens unless only openid, email and profile scopes are requested.
- The limit is 100 refresh tokens per Google Account per client ID; the oldest is invalidated without warning.
- Google: OAuth 2.0 for web server applications Checked 2026-10-11.
- A refresh token is returned only on the first authorisation, and offline access must be requested.
- An invalid_grant error during refresh means the token may have expired or been invalidated.
- Redirect URIs must match exactly and use HTTPS except for localhost.
- Google: comply with OAuth 2.0 policies Checked 2026-10-11.
- OAuth clients for a live app must not contain test environments, redirect URIs, or JavaScript origins available only to you or your development team.