Developer
Technical comparison and one-time setup guide for deploying this app.
Technical comparison
How the two integrations differ at the implementation level.
How the integrations work
Zapier— the app acts as a thin proxy. The client sends the expense payload plus the user's webhook URL to /api/zapier. The route validates the body and forwards it to Zapier. Zapier handles the rest — no Google credentials are ever needed on the server. This is the no-code philosophy: powerful for connecting systems, limited when you need reads or want to avoid per-task pricing.
Sheets API— the app implements the full OAuth 2.0 Authorization Code Flow. The user authorises once via the Google consent screen; the server exchanges the auth code for a refresh token and stores it in the client's localStorage. Every subsequent API call (write or read) calls refreshAccessToken() server-side first — the client never sees an access token. The refresh token persists indefinitely until the user revokes access from their Google account settings.
The OAuth callback route (/api/auth/google/callback) exchanges the code for tokens, fetches the user's email, then redirects to /settings/connect with the refresh token and email as URL params. ConfigForm reads and saves these on mount, then immediately calls history.replaceState to remove them from the URL.
Developer setup
What you need to do once before deploying, per integration.
Zapier — no server setup required
The webhook URL is provided by the user at runtime — it's passed in the request body and never stored server-side. There are no environment variables to set and no Google Cloud configuration needed. Deploy the app and users can start using Zapier immediately.
Sheets API — one-time Google Cloud setup
This setup is done once by the developer. After it's complete, any user can connect their Google account without any further configuration.
- APIs & Services → Library → search for Google Sheets API → Enable
- APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID
- Application type: Web application
- Authorised redirect URIs — add both:
http://localhost:3000/api/auth/google/callbackhttps://your-production-domain.com/api/auth/google/callback - Copy the Client ID and Client Secret
.env.local (local) and your hosting platform (production):GOOGLE_CLIENT_SECRET=your-client-secret
ENCRYPTION_KEY=your-32-byte-base64-key
ENCRYPTION_KEY with: openssl rand -base64 32Restart the dev server after saving.- OAuth consent screen → Test users → Add users
- Add the Google accounts that will use the app
- Publish the app when ready to allow any Google account to connect
Apps in Testing mode can only be authorised by listed test users. Publishing removes this restriction but triggers a Google review for sensitive scopes.
Credentials and security model
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and ENCRYPTION_KEY live in environment variables and are only used server-side. They are never sent to the client.
After the OAuth flow completes, the server encrypts the refresh token with AES-256-GCM usingENCRYPTION_KEY before sending it to the client. The encrypted blob is stored in localStorage. When the client calls /api/sheets, it sends the blob; the server decrypts it, exchanges it for a short-lived access token, makes the Google Sheets call, and discards both. The access token and plaintext refresh token never reach the client.
The Zapier webhook URL is stored in localStorage and sent in the request body. It is a shared secret — anyone who obtains it can POST to the Zap. The app never logs any credential server-side.
Source code: github.com/sanudin-dev/money-tracker