Google Business Profile Integration
📋 Overview
Google Business Profile integration providing access to business location management through the Google My Business API. Supports OAuth authentication, location listing, and business information retrieval for verified locations.
Provider: Google (https://business.google.com)
API Version: v1 (Business Information API, Account Management API)
Integration Type: OAuth 2.0 with automatic token refresh
📚 Documentation Structure
This integration is organized into the following sections:
- Authentication - OAuth 2.0 flow, token management, and disconnection
- Locations - List and retrieve business location information
✨ Features
- ✅ OAuth 2.0: Secure authentication with Google accounts
- ✅ Location Management: List all verified business locations
- ✅ Place ID Integration: Links to Google Places data
- ✅ Pagination Support: Handle large location lists efficiently
- ✅ Token Management: Automatic refresh and re-authentication
- ✅ Sub-account Support: Works with DashClicks sub-accounts
- ✅ Verified Locations Only: Filters for locations with Place IDs
🏗️ Architecture
Frontend Request
↓
OAuth Flow (consent screen)
↓
Token Storage (MongoDB)
↓
Token Refresh (on-demand)
↓
Google My Business API
↓
Location Data (verified only)
🗄️ MongoDB Collections
📖 Detailed Schema: See Database Collections Documentation
google.business.tokens
Purpose: Store OAuth 2.0 tokens for Google Business Profile access
Key Fields:
account_id(ObjectId, required) - DashClicks account referencegoogle_account_id(String, required) - Google account ID from APIaccess_token(String, required) - OAuth access tokenrefresh_token(String, required) - OAuth refresh token for renewalcreated_by(ObjectId) - User who performed the integrationcreatedAt(Date) - Token creation timestampupdatedAt(Date) - Last token update timestamp
Indexes:
{ account_id: 1 }(unique implied) - Primary lookup
Token Lifecycle:
// 1. Initial OAuth creates token
{
account_id: ObjectId("..."),
google_account_id: "1234567890",
access_token: "ya29.a0AfH6...",
refresh_token: "1//0gCgY...",
created_by: ObjectId("...")
}
// 2. Subsequent requests refresh access_token
{
account_id: ObjectId("..."),
access_token: "ya29.a0AfH6...", // New token
refresh_token: "1//0gCgY...", // Same
updatedAt: Date("2025-10-10")
}
📁 Directory Structure
Source Code Location:
external/Integrations/GoogleBusiness/
├── Controllers/
│ ├── auth.js # OAuth authentication handlers
│ └── account.js # Location management controllers
├── Routes/
│ ├── auth.js # Auth endpoints
│ └── account.js # Account endpoints
└── index.js # Route registration and error handling
Shared Models Used:
shared/models/google-business-token.jsshared/models/account.js
🔌 API Endpoints Summary
| Method | Endpoint | Description |
|---|---|---|
| GET | /auth/login | Initiate OAuth 2.0 flow |
| GET | /auth/callback | Handle OAuth callback |
| GET | /account/locations | List business locations (paginated) |
🔐 OAuth Scopes
The integration requests the following Google OAuth scopes:
| Scope | Purpose |
|---|---|
https://www.googleapis.com/auth/business.manage | Manage business information |
https://www.googleapis.com/auth/userinfo.profile | Access user profile info |
https://www.googleapis.com/auth/userinfo.email | Access user email address |
🌐 Google APIs Used
My Business Account Management API
Base URL: https://mybusinessaccountmanagement.googleapis.com/v1
Purpose: Retrieve account information and IDs
Key Endpoint:
GET /accounts- List all Google Business accounts
My Business Business Information API
Base URL: https://mybusinessbusinessinformation.googleapis.com/v1
Purpose: Access location details and metadata
Key Endpoint:
GET /accounts/-/locations- List all locations across accounts- Read mask:
name,metadata,title - Filters: Only verified locations with
placeId
- Read mask:
⚙️ Environment Variables
# Required for OAuth
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
GOOGLE_BUSINESS_CALLBACK_URL=https://api.dashclicks.com/v1/e/integrations/google/business/auth/callback
# For development
NODE_ENV=development
PROXY_URL=http://localhost:5003/v1/e/integrations/google/business/auth/callback
# JWT secret for state parameter
APP_SECRET=your_app_secret
🚀 Quick Start
1. Configure Environment Variables
Set up the required Google OAuth credentials in your .env file.
2. Initiate OAuth Flow
GET /v1/e/integrations/google/business/auth/login?forward_url=https://app.dashclicks.com/integrations
Authorization: Bearer {jwt_token}
Response: Redirects to Google OAuth consent screen
3. Handle Callback
After user grants permission, Google redirects to the callback URL, which stores the tokens and redirects to your forward_url with status.
# Success redirect
https://app.dashclicks.com/integrations?status=success&integration=google_business&token={token_id}
# Error redirect
https://app.dashclicks.com/integrations?status=error&reason={error_message}&integration=google_business
4. List Locations
GET /v1/e/integrations/google/business/account/locations?token={token_id}
Authorization: Bearer {jwt_token}
Response:
{
"success": true,
"locations": [
{
"name": "accounts/1234567890/locations/9876543210",
"id": "9876543210",
"title": "My Business Location",
"placeId": "ChIJN1t_tDeuEmsRUsoyG83frY4",
"metadata": {
"placeId": "ChIJN1t_tDeuEmsRUsoyG83frY4"
}
}
],
"nextPageToken": null
}
🔄 Token Refresh Flow
The integration uses a manual token refresh approach:
// 1. Retrieve stored refresh_token
const businessToken = await GoogleBusinessToken.findById(token);
const { refresh_token } = businessToken;
// 2. Request new access_token
const data = {
refresh_token: refresh_token,
client_id: GOOGLE_CLIENT_ID,
client_secret: GOOGLE_CLIENT_SECRET,
grant_type: 'refresh_token',
};
const resp = await axios.post('https://oauth2.googleapis.com/token', data);
const { access_token } = resp.data;
// 3. Use new access_token for API requests
const headers = { Authorization: `Bearer ${access_token}` };
🎯 Use Cases
1. Business Location Management
// List all verified locations for a business
const token = '507f1f77bcf86cd799439011'; // From OAuth callback
const response = await axios.get(
`/v1/e/integrations/google/business/account/locations?token=${token}`,
{ headers: { Authorization: `Bearer ${jwt_token}` } },
);
const locations = response.data.locations;
console.log(`Found ${locations.length} verified locations`);
2. Multi-page Location Retrieval
// Handle pagination for businesses with many locations
let allLocations = [];
let nextPageToken = null;
do {
const url = `/v1/e/integrations/google/business/account/locations?token=${token}${
nextPageToken ? `&nextPageToken=${nextPageToken}` : ''
}`;
const response = await axios.get(url, {
headers: { Authorization: `Bearer ${jwt_token}` },
});
allLocations = [...allLocations, ...response.data.locations];
nextPageToken = response.data.nextPageToken;
} while (nextPageToken);
console.log(`Total locations: ${allLocations.length}`);
3. Extract Place IDs for Google Places API
// Get Place IDs to use with Google Places API
const response = await axios.get(
`/v1/e/integrations/google/business/account/locations?token=${token}`,
{ headers: { Authorization: `Bearer ${jwt_token}` } },
);
const placeIds = response.data.locations.map(loc => loc.placeId);
// Use placeIds with Google Places API for reviews, photos, etc.
⚠️ Error Handling
Common Error Scenarios
1. Invalid or Expired Token
{
"success": false,
"errno": 400,
"message": "TOKEN_INVALIDATED"
}
Solution: Re-authenticate via /auth/login endpoint
2. Missing Forward URL
{
"success": false,
"errno": 400,
"message": "forward url is required!"
}
Solution: Include forward_url query parameter
3. Invalid Sub-account
{
"success": false,
"message": "Invalid SubAccount Id"
}
Solution: Verify sub-account ID and parent relationship
4. Invalid Token ID
{
"success": false,
"message": "Invalid token provided."
}
Solution: Use the token ID returned from OAuth callback
🔧 Error Middleware
The integration includes specialized error handling:
// Detects token invalidation from Google API responses
if (error?.response?.data?.error?.code) {
let errorCode = error.response.data.error.code;
if (errorCode == '401' || errorCode == '403') {
error.message = 'TOKEN_INVALIDATED';
}
}
// Handles various error formats from Google APIs
if (error?.response?.data?.error && typeof error.response.data.error == 'string') {
if (error.response.data.error == 'invalid_grant') {
error.message = 'TOKEN_INVALIDATED';
}
}
🔒 Security Features
1. State Parameter Validation
// State token contains encrypted account and redirect info
const token = jwt.sign(
{
data: 'account_id=' + accountId + '&forward_url=' + forward_url + '&created_by=' + creatorId,
redirect_uri: REDIRECT_URL,
},
process.env.APP_SECRET,
{ algorithm: 'HS256' },
);
// Callback validates state to prevent CSRF
const payload = jwt.verify(req.query.state, process.env.APP_SECRET);
2. Forced Re-authentication
// Login endpoint deletes existing tokens to force fresh consent
await GoogleBusinessToken.deleteOne({ account_id: accountId });
3. OAuth Prompt
// Always shows consent screen to ensure user is aware
const url =
`https://accounts.google.com/o/oauth2/v2/auth?` +
`redirect_uri=${PROXY_URL}&` +
`prompt=consent&` + // Force consent screen
`response_type=code&` +
`client_id=${GOOGLE_CLIENT_ID}&` +
`scope=${scopes}&` +
`access_type=offline&` +
`state=${token}`;
📊 Location Filtering
The integration automatically filters locations:
// Only returns verified locations with Place IDs
locations = locations.filter(location => location.metadata?.placeId);
Why? Unverified locations cannot be managed through the API and don't have Place IDs for integration with other Google services.
🔗 Related Documentation
- Google Business Profile API
- My Business Account Management API
- My Business Business Information API
- OAuth 2.0 Configuration
💡 Best Practices
1. Token Management
- Always handle
TOKEN_INVALIDATEDerrors by prompting re-authentication - Don't cache access tokens - retrieve fresh tokens on each request
- Store token IDs (not tokens themselves) in frontend state
2. Pagination
- Use
nextPageTokenfor businesses with many locations - Don't assume all locations fit in one page
- Handle null
nextPageTokenas end of results
3. Error Handling
- Always provide a valid
forward_urlfor OAuth flows - Check token validity before making API requests
- Handle both 401 and 403 as token invalidation scenarios
4. Security
- Validate state parameter in OAuth callback
- Use HTTPS for all callback URLs in production
- Never log or expose refresh tokens
5. Sub-accounts
- Validate sub-account ownership before allowing access
- Use parent account's JWT but specify
subaccountidquery parameter - Store tokens per sub-account for isolation