WebProCMS is a complete reputation tool: it pulls reviews of your business from Google, Yelp, and Facebook into a local mirror on your own site, collects new reviews through email/SMS requests, QR codes, and an on-site review form, warns you about negative reviews before they settle, lets you reply from the dashboard (publishing straight to Google), and displays everything anywhere via the Social Proof design library rows. Reviews are fetched server-side on a schedule and rendered as part of the page: no third-party widget script, no iframe, no cookie-consent implications, and no per-visitor calls to any platform.
(This feature supersedes the old free "Google Reviews" integration that lived under Settings → API Keys. Existing Google credentials are converted into a connected Google source automatically when the feature's migration runs.)
What the visitor sees
Five rows under Add Row → Page Content → Social Proof (all gated by @requiresFeature reviews):
- Social Proof Reviews — headline, overall rating summary, responsive grid of review cards, and a "Leave Us a Review" button.
- Reviews Carousel — one review at a time, auto-rotating with a fade, with the rating summary above.
- Reviews Rating Badge Bar — a slim band (big rating number, stars, "Based on N reviews", Review Us button) for above the footer or under a hero.
- Review Spotlight — your single best review front and center, chosen automatically (highest rating, most detailed story).
- Reviews Wall of Love — a masonry wall of full-length reviews including owner replies ("Response from the owner") and verified-customer checkmarks.
Common to all rows: the overall rating weights each platform's reported totals by its review count and folds in reviews collected on your own site; the CTA deep-links to Google's write-a-review page when a Google source is connected (else the first platform's write link); cards use preset-safe surfaces (bg-white/6, border-current/10, text-row-accent stars) so every row works on any section color preset; and every element's classes are editable through the normal design sidebar.
Star-rating SEO markup. When you've collected reviews on your own website (see first-party collection below), the rows emit LocalBusiness + AggregateRating JSON-LD built only from those first-party reviews — Google's guidelines don't allow marking up ratings imported from other platforms, so this stays compliant while still earning star rich-results. Toggle under Reviews → Settings.
Works out of the box (sample mode)
Before any platform is connected, the rows render clearly-labeled sample reviews ("This is a sample review. Connect Google, Yelp, or Facebook on the Dashboard → Reviews page…"). The rows preview correctly in the design library and page editor with zero setup, and the sample copy explicitly says it's a sample so it can't pass as a real review. Once a source is connected, the samples are replaced by real reviews.
Getting more reviews (the funnel)
The Requests tab (Dashboard → Reviews → Requests) is the collection engine.
Requires the Marketing feature. Both delivery channels ride Marketing's senders (
CampaignMailerfor email,SmsSender/Twilio for text), so with Marketing off the Requests tab shows an amber notice — linking admins straight to Settings → Features, and telling non-admins to ask one — the Email/Text options disappear from the send modal, list import is blocked with the same explanation, and any send that's attempted anyway fails with the reason on the row. Links and QR codes keep working, since they need no sender. Marketing is on by default, so this only bites installs that deliberately turned it off.
- Send a request by email or text message (SMS uses the Marketing feature's Twilio setup), or generate a link + QR code to print on receipts and counter cards. Message templates are editable under Settings (
:name,:businessplaceholders). Or ask a whole list at once — Import list. Upload a CSV of past customers (header row with anemailcolumn, aphonecolumn, or both;nameoptional and derived from the address when missing — the same headers the Marketing subscriber importer takes, so one export feeds both) and everyone gets their own tracked request. Choose Email, or text when there's no email / Email only / Text message only; rows are skipped (with a reason) when they carry no usable address, when texting isn't configured, when the number replied STOP, when the same person appears twice, when a request is already queued for them, and — unless you switch it off for this import — when they were asked inside the per-customer cooldown window. Imported requests skip the auto-request delay (an import has no triggering event to wait on): the first batch goes out in the background right after the upload and the hourly cron drains the rest, so a long list keeps sending after you close the page. - The customer lands on a tokenized "How did we do?" page and taps a star rating.
- High ratings (threshold configurable, default 4★+) are invited to post on your connected platforms — each button deep-links to that platform's write-a-review page — or to write the review right on your site.
- Low ratings get a private feedback form first: the message goes straight to the owner (email alert + optionally a support ticket via the Ticket System), so you hear about the problem before a public platform does. The public-review option remains available to everyone — "review gating" violates Google's and Yelp's policies, so the funnel offers, never blocks.
- The request row tracks its lifecycle (Pending → Sent → Opened → Rated → Completed), and the tab shows totals and completion rate.
Automatic requests. Flip on per-trigger toggles under Settings and a request is created automatically when an E-Commerce order is paid/fulfilled, an Online Booking appointment completes, a support ticket closes, or a tracked project completes (Project Tracking feature — if completed projects also enroll clients in a review follow-up sequence, leave this trigger off so clients aren't asked twice). A configurable delay (default 24h) holds the send, a per-customer cooldown (default 90 days) prevents re-asking repeat customers, and each order/booking/ticket/project can only ever fire once. Requests are delivered by the hourly reviews:send-requests LazyCron task, and each send is logged on the customer's CRM timeline when the CRM feature is on.
Reviews on your own website (first-party collection)
- The funnel's "write it here" form and the optional standing page at
/reviews/write(off by default; toggle in Settings) collect reviews directly on your site. - Submissions land in a moderation queue at the top of Dashboard → Reviews — nothing shows publicly until you approve it. Approve/Reject with one click; the alert email address is notified when something is waiting.
- Reviews that arrive through a tokenized request are marked Verified customer — a real interaction (order, booking, ticket) sits behind them — and show a checkmark on the Wall of Love row.
- The standing form is protected by a honeypot, a minimum-fill-time trap, and a per-IP rate limit; moderation is the backstop.
- Approved first-party reviews are what power the JSON-LD star markup (see above).
The dashboard (Dashboard → Reviews)
Four tabs: Reviews, Requests, Insights, Settings.
- Connect a platform — Google (Places API key + Place ID), Yelp (Fusion API key + business page URL or ID), or Facebook (long-lived Page access token). Credentials are validated on connect and stored encrypted.
- Google Business Profile OAuth (Settings tab) — the recommended Google connection: sign in with Google to sync all of your reviews (the Places key returns only the 5 "most relevant") and unlock replying from the dashboard. One-time setup: your own Google Cloud OAuth client + Business Profile API access; the settings page shows the redirect URI to paste into the client. Multi-location accounts get a location picker.
- Reply to any review — the chat button on each collected review opens a reply box with an optional Draft with AI button (uses the AI text provider configured under Settings → API Keys; drafts are always editable, nothing posts without you). Replies on Google Business Profile sources publish to Google; replies on other sources are stored locally and render beneath the review on your site (use Copy + Open on platform to also answer there).
- Share to social — the megaphone button on any 4★+ review queues a ready-made quote post to every account connected in the Social Media Posting addon. An optional auto-post mode does this automatically for new top-rated reviews.
- Add review / Import CSV — manual entry for one-off testimonials, or a bulk CSV import (
author,rating,textrequired columns;date,platformoptional) for platforms without an API (Trustpilot, Angi, BBB, TripAdvisor exports). Re-importing the same file is deduped. - Sources table — per-source status, platform rating/count, mirrored count, last sync, Sync now, remove, and (when the Locations content type has items) a location dropdown to tie each source to an office for multi-location businesses.
- Collected reviews table — hide/show on the public site (hidden reviews stay hidden across syncs), Verified/Replied badges, delete for manual and website entries. Platform reviews can't be deleted — they'd come back on the next sync; hiding is the right verb.
Insights (Dashboard → Reviews → Insights)
- Stat tiles: overall rating, total reviews, collected this month, reply rate, request completion.
- Reviews per month — a 12-month bar chart of collection volume (hover for that month's average rating).
- Where your reviews live — platform share breakdown.
- What customers keep saying — an AI analysis of your latest 100 reviews: recurring praise (+), recurring complaints (−), and the single highest-impact improvement (→). Runs on demand, cached until you refresh it.
Negative review alerts
When a sync collects a new review at or below the alert threshold (default ≤3★), the alert email gets the review text immediately, and — optionally — a support ticket is opened so the response becomes a tracked work item. Alerts only fire for reviews collected after a source's initial backfill, so connecting a platform never floods you with history. Private funnel feedback triggers its own owner email (plus the optional ticket with the customer as requester).
How it works (architecture)
The module lives at app/Features/Reviews/ and follows the Social Feed local-mirror pattern:
- Local mirror.
review_sourcesholds each connected platform (encrypted credentials, platform-reported overall rating/count, sync state, optional location link);external_reviewsholds the mirrored reviews plus moderation state (pending), verification, and owner replies;review_requeststracks the outreach funnel. The public rows read only the mirror — never a platform API — so cached pages stay valid until content actually changes. - Accumulation, not pruning. Platforms cap what their APIs return per request (Google Places: 5 "most relevant", Yelp: 3 excerpts; Google Business Profile OAuth returns everything). Synced reviews are never deleted when they drop out of the platform's response — the mirror grows past those caps over time.
- Scheduled work.
reviews:syncmirrors daily;reviews:send-requestsdelivers due requests hourly (both via LazyCron, both no-op when the feature is off). A sync that changes content clears the response cache. One send pass is capped (ReviewRequestSender::SEND_BATCH, 250) so a large import drains over successive ticks instead of running unbounded inside one web request. Every SMS send — single, automatic, or imported — is checked against Marketing's phone-keyed STOP suppression first; a suppressed number fails the request with a visible reason rather than texting. - Cross-feature integrations all go through the other features' public support APIs and check their feature flags at call time: TicketActions (tickets), CampaignMailer/SmsSender (marketing), CrmContacts (CRM timeline), SocialPost (social posting). Order/booking/ticket/project triggers are Eloquent
updatedlisteners registered by the Reviews service provider — no other feature's code is modified. The inverse surface isReviewLinks: Marketing's{{review_link}}token and review-based sequence conditions call it to mint per-recipient funnel links (created already-sent, soreviews:send-requestsnever double-delivers) and to ask whether a recipient has responded.
Because no platform script or iframe ever reaches the visitor's browser, the feature has no cookie-consent surface, and the privacy-policy generator picks it up via the reviews privacy signal.
Key implementation files:
- app/Features/Reviews/Support/ReviewsSyncer.php — the upsert-only mirror sync + new-review alert dispatch.
- app/Features/Reviews/Support/Providers/ —
GoogleProvider(Places API New),GoogleBusinessProvider(Business Profile OAuth: paged fetch + reply posting + token refresh),YelpProvider(Fusion),FacebookProvider(Graph ratings). - app/Features/Reviews/Support/Reviews.php — the render-path facade the rows call (
summary(),reviews()with min-rating/platform/location filters,spotlight(),writeReviewUrl(),stars(),schemaData()). - app/Features/Reviews/Support/ReviewFunnelActions.php / ReviewRequestSender.php / ReviewRequestTriggers.php / ReviewRequestCsvImporter.php / ReviewAlerts.php — the collection loop.
- resources/design-library/rows/page-content/social-proof/ — the five row templates.
Platform setup
Google (recommended: Business Profile sign-in). Create an OAuth client (type "Web application") at console.cloud.google.com, enable the Business Profile APIs, and request Business Profile API access for the project (a one-time Google approval). Paste the client ID/secret under Reviews → Settings, add the shown redirect URI to the client, and click Connect with Google. This syncs every review and enables dashboard replies.
Google (simple: Places API key). Enable the Places API (New), create an API key (billing account required even for free-tier usage; one call per day stays far inside the free credit), and find the Place ID with Google's Place ID finder. Returns only the 5 "most relevant" reviews per sync. Reviews with no text are skipped on both Google paths.
Yelp. Create a free app in the Yelp developer portal for a Fusion API key, then paste your Yelp business page URL (or business alias/ID — a full pasted URL is resolved to the alias automatically).
Facebook. Generate a long-lived Page access token with the pages_read_user_content permission (e.g. via the Graph API Explorer, same flow as the Social Feed feature). Modern Facebook "recommendations" carry no star value — recommended maps to 5 stars, not-recommended to 1 — while legacy reviews keep their original star rating.
Everything else (Trustpilot, Angi, BBB, TripAdvisor, direct testimonials…) — Add review for one-offs, Import CSV for bulk, with the platform name shown on the card.
Platform caps (theirs, not ours)
- Google Places API returns at most 5 reviews per request — Google's hard cap. The Business Profile OAuth connection has no cap (it pages through everything) and is the only Google surface that supports posting replies.
- Yelp returns up to 3 review excerpts (~160 characters each), linking to the full review. No reply API.
- Facebook exposes recent recommendations; no reply API.
- Tools that show more than the caps without OAuth do it by scraping, which violates the platforms' Terms of Service — WebProCMS deliberately stays on official APIs.
Settings reference (Dashboard → Reviews → Settings)
| Setting | Default | What it does |
|---|---|---|
| Funnel threshold | 4★ | Ratings at/above this get the public-platform invitations first |
| Feedback opens ticket | on | Private funnel feedback opens a support ticket (Tickets feature) |
| Public review form | off | The standing /reviews/write page |
| Star-rating structured data | on | JSON-LD from approved first-party reviews |
| Auto-request triggers | off | Per-trigger: order paid/fulfilled, booking completed, ticket closed, project completed |
| Request delay / cooldown | 24h / 90d | Wait before sending; days between asks per customer |
| Email/SMS templates | built-in | :name, :business placeholders |
| Alert email + threshold | — / ≤3★ | Negative-review notifications |
| Alert opens ticket | off | Negative platform review becomes a tracked ticket |
| Auto-post to social | off | Queue new top-rated reviews to Social Posting accounts |
| GBP OAuth client | — | Client ID/secret for the Google Business Profile connection |
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| Row shows sample reviews on the live site | No source connected yet — connect one under Dashboard → Reviews. |
| Source shows an Error badge | The API's own message is shown under the badge (bad key, Places API (New) not enabled, expired token, wrong business ID). Fix and hit Sync now. |
| Fewer cards than the platform shows | Text-less reviews are skipped, and platforms cap what their APIs return (see above) — connect Google via OAuth to get everything, or let the mirror grow over successive syncs. |
| A bad review appeared | Hide it with the eye toggle — it stays hidden on future syncs. And check Insights: if it's a recurring theme, that's the fix that matters. |
| Reviews seem outdated | Syncs run daily; use Sync all on the dashboard to refresh immediately. |
| Review requests stuck on Pending | They send after the configured delay via the hourly cron; use the send button on the row to push one out immediately. No email provider configured = share the link/QR instead. A big import sends 250 at a time, so the tail clears over the next few hourly ticks. |
| No Email/Text option, or "Marketing is turned off" | The Marketing feature provides both senders. Turn it on under Settings → Features (admin only). |
| Most of an imported list was skipped | The notification names the reasons. The usual one is the per-customer cooldown (default 90 days) — re-run the import with "Skip anyone already asked…" switched off if you mean to re-ask. Rows with no valid email and no phone, duplicates, and STOPped numbers are always skipped. |
| "Connect with Google" fails | The OAuth client needs the Business Profile APIs enabled AND approved API access (Google's one-time request form), and the redirect URI from the settings page added to the client. |
| Google reviews doubled up | Both a Places-key source and a Business Profile source are connected for the same location — remove the Places one (its mirror rows can be hidden). |