02 Get your data in /Integrations
Google Business Profile reviews
Send Google reviews of your business locations to Dimension Labs with the universal API.
- GB Google Business Profile reviewsTickets, chats, calls, reviews
- ⟳ Your jobPull new · map fields · every 15 min
- { } Universal APIPOST /track · /trackhistorical
- Dimension LabsRead in full · enriched daily
This brings in the Google reviews of your Business Profile locations: each review as a customer message, with its star rating, and your owner reply when there is one. You run it on your side with the universal API, using the Google Business Profile API.
What you need
Section titled “What you need”- A Google Cloud project approved for the Business Profile APIs. Google requires a Business Profile that has been verified and active for 60 days or more, and a website for the business.
- An OAuth 2.0 client ID and secret, and a Google account that is an owner or manager of the locations, to authorize once with the
https://www.googleapis.com/auth/business.managescope and get a refresh token. - A Dimension Labs API key for a new Universal API source. See Your API key.
1. Get API access in Google Business Profile
Section titled “1. Get API access in Google Business Profile”Google’s steps are in Prerequisites and Basic setup.
- In the Google Cloud Console, create or pick a project and copy its Project Number from the Project info card on the dashboard.
- Submit the GBP API contact form and choose Application for Basic API Access, using an email address that is an owner or manager on the Business Profile. Google says your project is approved when its Business Profile API quota shows 300 QPM instead of 0.
- Once approved, enable the Business Profile APIs in the API Library, including the Google My Business API, My Business Account Management API and My Business Business Information API.
- Go to Credentials and click Create credentials > OAuth client ID. Choose Web application and add
https://developers.google.com/oauthplaygroundas an authorized redirect URI. Copy the client ID and client secret. - Get a refresh token once: open the OAuth 2.0 Playground, click the settings icon, select Use your own OAuth credentials and enter your client ID and secret. Authorize the
https://www.googleapis.com/auth/business.managescope with an account that manages the locations, then click Exchange authorization code for tokens and copy the refresh token. Keep it in a secrets manager. If your OAuth consent screen is set to External and its status is Testing, Google expires refresh tokens after 7 days, so set it to In production (or use an Internal app in a Google Workspace organization). - Access tokens last about an hour. The code below uses
google-auth-libraryto turn the refresh token into a fresh access token whenever it needs one, and sends it asAuthorization: Bearer ACCESS_TOKEN. - Find your account and location IDs with
GET https://mybusinessaccountmanagement.googleapis.com/v1/accountsandGET https://mybusinessbusinessinformation.googleapis.com/v1/accounts/ACCOUNT_ID/locations.
2. Pull new reviews
Section titled “2. Pull new reviews”curl -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://mybusiness.googleapis.com/v4/accounts/ACCOUNT_ID/locations/LOCATION_ID/reviews?pageSize=50&orderBy=updateTime%20desc"accounts.locations.reviews.listworks only for verified locations.pageSizegoes up to 50.- Incremental: there is no “updated since” filter. The default order is
updateTime desc, newest change first, so page untilupdateTimeis older than your cursor, then stop. - Pagination: pass
nextPageTokenaspageToken. It is missing on the last page. - The owner reply is in the same review object, under
reviewReply, so there is no second call. - Google’s quota page asks you to spread requests evenly and back off on
429. It doesn’t list a separate limit for reviews.
3. Map the fields
Section titled “3. Map the fields”Each review is one incoming message under its own sessionId. An owner reply is an outgoing message in the same session. Reviews with a star rating but no comment have no text to send: skip them.
| Dimension Labs | Google Business Profile | Notes |
|---|---|---|
text | comment (incoming), reviewReply.comment (outgoing) | Google calls both “plain text with markups”. |
userId | reviewId | No stable reviewer ID is returned, so use the review ID. Don’t send reviewer.displayName. |
incoming | true for the review, false for the owner reply. | |
sessionId | reviewId | Replace any colons. |
conversationId | reviewId | |
dimensionlabs_timestamp | createTime (review), reviewReply.updateTime (reply) | RFC 3339. Convert with toMillis. If the reply’s time is within a second of the review’s, the code moves it a second later. |
platformJson | starRating (ONE to FIVE, as a number), location ID, reviewer.isAnonymous, reviewReply.reviewReplyState | The location ID lets you join reviews to store data. |
4. Send it
Section titled “4. Send it”Install the auth library in your project first: npm install google-auth-library.
// google-reviews.mjs. Environment: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN,// GOOGLE_LOCATIONS (comma-separated, each 'accounts/ACCOUNT_ID/locations/LOCATION_ID'), DL_API_KEY.import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';import { OAuth2Client } from 'google-auth-library';
const HOUR = 60 * 60 * 1000;const sleep = (ms) => new Promise((r) => setTimeout(r, ms));const STARS = { ONE: 1, TWO: 2, THREE: 3, FOUR: 4, FIVE: 5 };const oauth = new OAuth2Client(process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET);oauth.setCredentials({ refresh_token: process.env.GOOGLE_REFRESH_TOKEN });
// Uses a fresh access token on every call (the library refreshes it when it expires),// and retries Google's rate limits (429) and server errors (5xx) with a growing pause.async function get(url) { for (let attempt = 0; ; attempt++) { const { token } = await oauth.getAccessToken(); const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } }); if (res.ok) return res.json(); if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Google returned ${res.status}: ${await res.text()}`); await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000); }}
async function syncLocation(location, since, historical) { let pageToken = '', newest = since, done = false; while (!done) { const q = new URLSearchParams({ pageSize: '50', orderBy: 'updateTime desc', ...(pageToken && { pageToken }) }); const page = await get(`https://mybusiness.googleapis.com/v4/${location}/reviews?${q}`); for (const r of page.reviews ?? []) { const updated = toMillis(r.updateTime); if (updated < since) { done = true; break; } newest = Math.max(newest, updated); const id = r.reviewId.replace(/:/g, '-'); const base = { userId: id, sessionId: id, conversationId: id, platformJson: { location, rating: STARS[r.starRating] ?? null, anonymous: r.reviewer?.isAnonymous } }; const created = toMillis(r.createTime); if (r.comment?.trim()) { await sendToDimensionLabs({ ...base, text: r.comment.trim(), incoming: true, dimensionlabs_timestamp: created }, { historical: historical || created < Date.now() - HOUR }); } if (r.reviewReply?.comment?.trim()) { const replied = Math.max(toMillis(r.reviewReply.updateTime), created + 1000); await sendToDimensionLabs({ ...base, text: r.reviewReply.comment.trim(), incoming: false, dimensionlabs_timestamp: replied, platformJson: { ...base.platformJson, replyState: r.reviewReply.reviewReplyState } }, { historical: historical || replied < Date.now() - HOUR }); } } pageToken = page.nextPageToken; if (!pageToken) done = true; } return newest;}
// Sends every review created or changed at or after `since` (Unix ms), for each location. Returns the new `since`.export async function syncGoogleReviews({ since, historical = false }) { let newest = since; for (const location of process.env.GOOGLE_LOCATIONS.split(',').map((x) => x.trim()).filter(Boolean)) { newest = Math.max(newest, await syncLocation(location, since, historical)); } return newest;}A review that is edited, or gets a new reply, comes back with a newer updateTime and is sent again under the same sessionId. Anything unchanged is a duplicate, and is removed automatically. A new reply to an old review goes to /track; the old review itself goes to /trackhistorical because of its age. One Dimension Labs source can hold all your locations: the location goes in platformJson.location.
5. Run it
Section titled “5. Run it”Save the code as google-reviews.mjs in a project set up as in Set up your project, and run it with the scheduled sync runner: import { syncGoogleReviews as sync } from './google-reviews.mjs';.
- History first:
node run.mjs 2020-01-01pages back through every review since that date and sends it to/trackhistorical. Then start a backfill from the Backfill tab. - Then every 15 minutes: the scheduled job sends new reviews and replies to
/track, and they are enriched automatically every day. Anything more than an hour old when the job sees it goes to/trackhistorical, and needs a backfill. - Re-runs are safe: duplicates are removed automatically.
If you have reviews in a file instead, upload it and check it first with the file checker, then run a backfill.
Google Business Profile reference
Section titled “Google Business Profile reference”- Prerequisites
- Basic setup
- Work with review data
- Method: accounts.locations.reviews.list
- REST Resource: accounts.locations.reviews
- Method: accounts.list
- Method: accounts.locations.list
- Quota limits
- Using OAuth 2.0 to access Google APIs (refresh token expiration)
- Latest updates
Checked against Google’s documentation on 2026-10-13.