Google Verification & CASA Tier 2
The Complete 60 Playbook

Everything needed to take 60 (use60.com) through Google OAuth brand verification, restricted-scope review, and the CASA Tier 2 security assessment — and to repeat it for any future application. Prepared 11 June 2026.

How to use this doc: work top to bottom with Rishi. Phases 0–2 are prep you control. Phase 3 starts Google's clock. Phase 4 (CASA) only begins when Google's Trust & Safety team emails you. Tick the checkboxes as you go — everything 60 has already completed is pre-ticked with context on why it matters.

Contents

  1. The 60-second glossary
  2. Timeline & what to expect
  3. What 60 has already done (our evidence pack)
  4. Phase 0 — Pre-flight checklist
  5. Phase 1 — Configuring the Cloud Console
  6. The scope list & written justifications (copy-paste ready)
  7. Phase 2 — The demo video (full shooting script)
  8. Phase 3 — Submitting & surviving the review
  9. Phase 4 — The CASA Tier 2 security assessment
  10. Phase 5 — After verification
  11. The rejection playbook
  12. Reference links

1. The 60-second glossary

TermWhat it means
Brand verificationThe lightweight first gate: Google confirms your app name, logo, homepage, and privacy policy genuinely represent you. ~2–3 business days. Required before anything else.
Sensitive scopesScopes that need verification but no security audit — e.g. gmail.send, calendar, tasks. Review takes up to ~10 days.
Restricted scopesThe big ones — gmail.readonly, gmail.compose, gmail.modify, drive.readonly. These trigger the full review plus the annual security assessment. Budget ~6 weeks end-to-end (often much faster).
CASACloud Application Security Assessment — the App Defense Alliance framework Google uses for that security assessment. Based on OWASP ASVS, mapped to CWEs.
Tier 2The CASA level virtually all SaaS apps land in: you scan your own app and fill in a questionnaire; an authorized lab validates the evidence. No lab access to your code or infra.
TAC SecurityGoogle's preferred/recommended CASA lab. Basic plan $540/app/year, LOV typically inside 1–3 weeks.
SAQSelf-Assessment Questionnaire — ~54 security requirements you answer with evidence (encryption, access control, incident response, data deletion…).
LOVLetter of Validation. The lab issues it when you pass and submits it to Google. Google then approves the OAuth verification within ~5–6 business days.
Limited UseThe policy that governs what you may do with Google user data: user-facing features only, no ads, no selling, no humans reading it, no training generalized AI models. Your privacy policy must state compliance verbatim-ish.
100-user capUnverified apps requesting sensitive/restricted scopes are capped at 100 new users, then Google sign-in gets disabled. This is the wall we're removing.

2. Timeline & what to expect

Week 0
Pre-flight + Console config
Phases 0–1 · us
Week 0
Record demo video
Phase 2 · Rishi
Week 0–1
Submit → brand review
2–3 business days · Google
Week 1–3
Scope review emails
reply fast · Google T&S
Week 2–4
CASA: scan + SAQ + TAC
$540 · 1–3 weeks
Week 3–5
LOV → approval 🎉
5–6 days after LOV

Google's official guidance says "up to 6 weeks" for restricted scopes. Real-world reports (app already secure, evidence ready) run as fast as 8 working days end-to-end. Our remediation work (section 3) is what buys the fast path.

The clock killers are (1) a demo video that misses a requirement — every round-trip with the review team costs days; (2) slow replies — always reply directly to the existing email thread from the verification team, never start a new one; (3) failing ZAP findings discovered late — we run the scan before submitting, not when TAC asks.

3. What 60 has already done — our evidence pack

This is why we expect to pass with flying colours. Everything below is merged and live in production (PR #1660 → promotion PR #1676, deployed 11 June 2026). Each row doubles as SAQ evidence in Phase 4.

ControlWhat we shippedCASA/ASVS area
OAuth tokens encrypted at restpgsodium XChaCha20-Poly1305 application-layer encryption for google_integrations and microsoft_integrations (migration 20260610140000), same framework as Slack/LinkedIn/HubSpot tokens. Plus Supabase disk-level AES-256 under everything.V6 Cryptography, V8 Data Protection
Privacy Policy + TermsLive at app.use60.com/privacy and /terms with the verbatim Google Limited Use disclosure, retention & deletion terms, GDPR rights, subprocessors.Verification requirement
Data deletion on requestadmin_hard_delete_user RPC cascades through tokens, emails, calendar events, recordings; Google OAuth grants are revoked at Google during deletion. Users can also disconnect Google any time in Settings → Integrations.CASA's headline requirement: "delete user data upon a user's request"
Rate limiting on auth pathsoauth-initiate, google-oauth-callback, password reset — per-user/IP/email limits via shared rate_limit table, fail-closed.V2 Authentication
Security headers + HSTSHSTS (2yr, includeSubDomains), X-Frame-Options DENY, nosniff, Referrer-Policy, Permissions-Policy served by production nginx. Verified live.V14 Configuration
No secrets in logs/codeToken-fragment logging removed; no hardcoded keys; service-role key never reaches the frontend; CI guards (check:no-clerk, egress allowlist, Deno permission tiers).V7 Error/Logging, V14
Dependency scanningnpm audit --audit-level=high blocks every PR; existing high/critical advisories cleared (27 → 11 moderate, 0 high).V14 Dependencies
Access controlRow-Level Security on every table holding Google user data, user-scoped policies; all edge-function auth derives identity from validated JWTs, never request bodies; webhooks verify HMAC signatures.V4 Access Control
TLS everywhereAll traffic HTTPS; Railway edge TLS; Supabase TLS; outbound egress allowlist enforced in CI.V9 Communications
Security documentationdocs/security/: hardening guide, OAuth token rotation runbook, RLS audits, sandbox posture, admin audit — pre-written answers for most of the 54 SAQ questions.V1 Architecture

4. Phase 0 — Pre-flight checklist

Do not click "Submit for Verification" until every box is ticked. Google reviewers check these mechanically and a miss restarts the cycle.

5. Phase 1 — Configuring the Cloud Console

All of this lives in Google Cloud Console → APIs & Services → OAuth consent screen (rebranded "Google Auth Platform" in newer consoles). Use the production project — the one whose client ID the live app uses. Never request unverified scopes from this project while the review runs.

5.1 Branding page

FieldValue for 60Gotcha
App name60 (must match what users see in-app)No Google trademarks without ™; name mismatches are a top brand-rejection cause
User support emailmonitored team address
App logo60 logo, 120×120Uploading/changing a logo triggers (re-)brand review
App home pagehttps://use60.comMust publicly describe the product and link the privacy policy
Privacy policyhttps://app.use60.com/privacyMust be on the same (verified) domain family and identical to the homepage's link
Terms of servicehttps://app.use60.com/termsOptional but we have it — include it
Authorized domainsuse60.comEvery redirect URI / origin must live under an authorized domain

5.2 Audience page

User type External, publishing status In production. This is also where the 100-user cap is displayed while unverified.

5.3 Data Access page (scopes)

Click Add or remove scopes and declare the exact list in section 6. The console will bucket them into non-sensitive / sensitive / restricted automatically — that bucketing drives the requirements. Paste a per-scope justification (section 6 gives you copy) into the justification fields when prompted at submission.

Never declare a scope for a feature that isn't implemented yet ("we'll need it next quarter") — that's an explicit, common rejection. And never remove an already-approved scope without instructions from the review team; it can revoke user tokens.

6. The scope list & written justifications

The principle is narrowest scopes: every scope needs (a) an implemented user-facing feature, (b) a justification, (c) an explanation of why a narrower scope won't work. Here is 60's full declaration with submission-ready copy:

Restricted (these trigger CASA)

ScopeJustification (paste-ready)
gmail.readonly"60 is a sales productivity CRM. With the user's consent, 60 reads the user's email messages to (1) show relevant correspondence with a meeting attendee on the pre-meeting preparation screen, (2) detect unanswered prospect emails so the user can follow up, and (3) learn the user's own writing tone from their sent messages so drafts match their voice. Narrower scopes are insufficient: gmail.metadata omits message bodies, which are required to summarize conversation context and analyse writing style."
gmail.compose"After a sales meeting, 60 creates a follow-up email draft in the user's Gmail drafts folder for the user to review, edit, and send themselves. 60 creates drafts only at the user's direction and never auto-sends from this scope. gmail.send alone is insufficient because the product's core workflow is creating reviewable drafts inside the user's own Gmail, not sending directly."

Sensitive (verification, no CASA)

ScopeFeature
gmail.sendSending the follow-up email the user has approved, from their own address.
gmail.labelsOrganizing 60-related email threads with labels the user configures.
calendar + calendar.eventsTwo-way calendar sync: showing the user's schedule, creating/updating/cancelling meetings the user books through 60, detecting conflicts, and triggering meeting prep.
tasksSyncing follow-up tasks 60 generates into the user's Google Tasks.
drive.file + documentsCreating proposal/meeting documents in the user's Drive. drive.file only reaches files 60 itself creates — deliberately narrow.
drive.metadata.readonlyListing file names to let the user attach existing documents to deals.
userinfo.email / userinfo.profileAccount identification.
Deliberately NOT requested: gmail.modify (overlaps gmail.readonly+compose+labels — overlap invites downscoping), https://mail.google.com/ (full access, hardest tier), drive.readonly (restricted; drive.file covers our need). If reviewers suggest narrower scopes anyway, reply "Confirm Downscoping" only if the feature truly survives, else "Unable to use narrower scopes" with specifics.

7. Phase 2 — The demo video

7.1 The rules (Google's hard requirements)

7.2 How many videos?

One video — 60 has a single web OAuth client. (If we ever submit multiple client IDs — e.g. a Chrome extension — the video must show the grant + data access on each client.) Keep a copy of the recording; the annual re-verification reuses or refreshes it.

7.3 The shooting script (for Rishi)

Setup: clean Chrome profile · test Google account with realistic data (a few meetings, some email threads with a "prospect") · production app.use60.com · screen recorder with mic (Loom/OBS/QuickTime) at 1080p · consent screen language set to English · do a dry run first.

Time
On screen
Narration (say roughly this)
0:00
use60.com homepage, scroll to footer showing the Privacy Policy link.
"This is 60, an AI sales command center at use60.com. I'm demonstrating our Google integration for OAuth verification. Our privacy policy is linked here on the homepage."
0:25
Log in to app.use60.com. Briefly show the dashboard.
"60 helps salespeople prepare for meetings and follow up afterwards. I'll connect a Google account and walk through every scope we request."
0:50
Settings → Integrations → click Connect Google.
"From the integrations page the user clicks Connect Google. This starts the OAuth flow."
1:05
THE MONEY SHOT. The Google consent screen. Slow down. Move the cursor along the address bar so the client ID is readable. Scroll the full permission list slowly. Show the app name "60" and the privacy policy link on the consent screen. Then click Allow.
"Here is the consent screen — you can see our app name, 60, our verified branding, and the client ID in the address bar. The user is asked for these permissions: read email, manage drafts, send email, manage labels, calendar, tasks, and Drive files created by the app. I'll grant access."
1:50
Back in 60: the calendar view fills with the account's meetings. Create a meeting in 60; switch to Google Calendar in another tab showing it appeared. calendar / calendar.events
"With the calendar scopes, 60 performs two-way sync: the user's schedule appears in 60, and a meeting booked in 60 is created in Google Calendar, with invites to attendees."
2:40
Open a meeting's prep view showing recent email context with the attendee. gmail.readonly
"With gmail.readonly, 60 prepares the user for this meeting by showing recent email correspondence with the attendee — read-only, displayed only to the user."
3:20
Trigger the post-meeting follow-up. 60 generates a draft. Switch to Gmail → Drafts and open the created draft. gmail.compose
"After a meeting, 60 writes a follow-up email in the user's tone and saves it as a draft in their Gmail — using gmail.compose. Nothing is sent; the user reviews it here in their own drafts folder."
4:00
Back in 60, approve/send the follow-up. Show it in Gmail Sent. gmail.send
"When the user approves, 60 sends it from their own address with gmail.send."
4:25
Show a 60-applied label on the thread in Gmail. gmail.labels
"gmail.labels lets 60 organize the threads it works with."
4:40
Create a follow-up task in 60; show it in Google Tasks. tasks
"Follow-up tasks sync to Google Tasks."
5:00
Generate a proposal/meeting doc; show it in Drive. Attach an existing file to a deal via the file picker. drive.file / documents / drive.metadata.readonly
"60 creates proposal documents in the user's Drive — drive.file only reaches files 60 itself creates. The metadata scope lets the user pick an existing file to attach to a deal."
5:40
Settings → Integrations → Disconnect Google. Then show app.use60.com/privacy scrolled to the Limited Use section.
"Users can disconnect at any time, which deactivates and revokes our access, and deleting an account deletes all stored Google data. Our privacy policy documents our Limited Use compliance. That's the complete flow — thank you."
Before uploading, re-watch and verify: client ID legible in the address bar ✦ consent screen in English ✦ every scope from section 6 demonstrated ✦ app name "60" visible ✦ audio clear. Upload to YouTube → Visibility: Unlisted → copy link.

8. Phase 3 — Submitting & surviving the review

  1. On the consent screen page click Prepare for Verification. Confirm branding, scopes, domains.
  2. Paste the scope justifications (section 6) and the YouTube link. Select the permitted application-type from the "What features will you use?" list (60 = CRM / productivity workflow).
  3. Click Submit for Verification.
  4. Brand verification resolves first (~2–3 business days). Watch the developer-contact and support inboxes.
  5. The OAuth review team emails with questions or requests. Rules of engagement: reply to the same thread, within 24h if possible; if they recommend downscoping, answer "Confirm Downscoping" or "Unable to use narrower scopes" + reasons; if they fault the video, fix and reply with the new link. A paused review (waiting on you) shows on the consent screen page.
  6. When the restricted-scope review is satisfied, Trust & Safety triggers the security assessment — the CASA notification email. That's Phase 4's starting gun.

9. Phase 4 — The CASA Tier 2 security assessment

9.1 The shape of it

Tier 2 = you scan your own app + answer the SAQ; the lab validates and issues the LOV. The lab never touches our code or infrastructure. To pass: the scan must show no findings mapped to CWEs with high likelihood of exploit (mediums also count on revalidation years — fix them anyway).

9.2 Run the ZAP scan (do this before TAC asks)

# 1. Get the CASA ZAP config from the ADA AST guide
#    https://appdefensealliance.dev/casa/tier-2/ast-guide/dynamic-scan
# 2. Run the full scan via Docker against production:
docker run -v $(pwd):/zap/wrk/:rw -t zaproxy/zap-stable \
  zap-full-scan.py -t https://app.use60.com \
  -c zap-casa-config.conf -r zap-report.html

# Keep BOTH: the config file used and the plain-text results —
# they're uploaded as evidence ("AST configuration file(s)" +
# "AST scan result(s) in .txt format").

Note: ADA's pre-configured static scanner (FluidAttacks) doesn't support TypeScript/JavaScript — for 60 the DAST ZAP scan is the primary artifact; a custom CWE-compatible SAST (e.g. Semgrep with CWE output) is the fallback if asked.

9.3 Engage TAC Security

  1. Buy the ESOF AppSec ADA CASA Basic plan — $540/app/year (includes 2 revalidation cycles + remediation support). Premium ($720–855) only adds hand-holding we won't need. Portal: casa.tacsecurity.com.
  2. Provide the prerequisites: app URL, login credentials for a test account, a simple data-flow diagram (user → 60 frontend → Supabase edge functions → Google APIs; tokens encrypted in Postgres), and the list of Google scopes used.
  3. For a web app, choose the DAST scan path (URL + test credentials), or upload our own ZAP results.

9.4 The SAQ — our answers are pre-built

~54 requirements across the ASVS categories. Map straight to section 3 of this doc plus docs/security/:

SAQ theme60's evidence
How is user data encrypted at rest?pgsodium XChaCha20-Poly1305 for OAuth tokens (screenshot the _enc bytea columns); AES-256 disk encryption (Supabase) for all data. Keep the screenshot + 3-sentence explanation ready — labs ask for exactly this.
Encryption in transitTLS 1.2+ everywhere; HSTS 2 years (curl -I screenshot)
Access controlRLS policies (cite migrations), JWT-derived identity in edge functions, admin role gates
Data deletion on user requestadmin_hard_delete_user + cascade FKs + Google token revocation; user-facing disconnect
Authentication controlsSupabase Auth, rate-limited reset/OAuth endpoints, session management
Logging & monitoringAudit logs with retention (30/90d), Sentry, no secrets in logs
Dependency managementnpm audit gate in CI, lockfiles
Incident responsedocs/security/hardening-guide.md incident-response section

9.5 Finish

TAC validates → may request 1–2 evidence items → issues the LOV and submits it to Google directly. Google sends the approval email within ~5–6 business days. Done — restricted scopes unlocked, user cap removed.

10. Phase 5 — After verification

11. The rejection playbook

RejectionRoot causeOur mitigation
"Demo video does not show the OAuth consent workflow"Consent screen skipped, cropped, wrong language, or scope list ≠ submissionScript step 1:05 — slow pan, English, address bar visible
"Video does not sufficiently demonstrate functionality"A requested scope never shown in useScript demonstrates all 11 scopes individually; pre-upload checklist
"Homepage requirements not met"Privacy link missing on homepage, or homepage is a login wallMarketing-site footer task in Phase 0 — do before submitting
"Scope not justified / narrower scope available"Overlapping or speculative scopesWe dropped gmail.modify + drive.readonly; justifications explain why narrower fails
"Scopes for unimplemented features"Requesting ahead of the roadmapEverything requested is live in prod today
"Privacy policy insufficient"Generic policy that doesn't address Google user data specificallyOur policy has a dedicated Google/Limited Use section, separate from general data
CASA scan failuresHigh-likelihood CWEs in the DAST resultsRemediation already shipped (headers, rate limits, etc.) + we run ZAP ourselves first
Silence / stalled reviewReplies going to new threads or unmonitored inboxesReply-to-thread rule; both contact emails monitored

Prepared by Claude Code for the 60 team, 11 June 2026. Reusable for future apps via the google-verification skill in .claude/skills/.