Skip to main content

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:

  1. Authentication - OAuth 2.0 flow, token management, and disconnection
  2. 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 reference
  • google_account_id (String, required) - Google account ID from API
  • access_token (String, required) - OAuth access token
  • refresh_token (String, required) - OAuth refresh token for renewal
  • created_by (ObjectId) - User who performed the integration
  • createdAt (Date) - Token creation timestamp
  • updatedAt (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.js
  • shared/models/account.js

🔌 API Endpoints Summary

MethodEndpointDescription
GET/auth/loginInitiate OAuth 2.0 flow
GET/auth/callbackHandle OAuth callback
GET/account/locationsList business locations (paginated)

🔐 OAuth Scopes

The integration requests the following Google OAuth scopes:

ScopePurpose
https://www.googleapis.com/auth/business.manageManage business information
https://www.googleapis.com/auth/userinfo.profileAccess user profile info
https://www.googleapis.com/auth/userinfo.emailAccess 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

⚙️ 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.

💡 Best Practices

1. Token Management

  • Always handle TOKEN_INVALIDATED errors 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 nextPageToken for businesses with many locations
  • Don't assume all locations fit in one page
  • Handle null nextPageToken as end of results

3. Error Handling

  • Always provide a valid forward_url for 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 subaccountid query parameter
  • Store tokens per sub-account for isolation
💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:30 AM