• Skip to main content
  • Skip to navigation
  • Skip to search
    Petunia™
    FeaturesPricingIntegrationsAboutContact
    Log inStart free trialSign up
    Loading
    Petunia™

    Reimagining customer communication for the modern business.

    Product

    • Features
    • Pricing
    • Integrations
    • Roadmap
    • What's New

    Resources

    • Help Center
    • Documentation
    • Guides
    • API Reference
    • Community
    • Support

    Company

    • About Us
    • Careers
    • Blog
    • Press
    • Contact

    © 2026 Gray Group International LLC. All rights reserved.·
    Made by gardenpatch 🌱

    Privacy PolicyTerms of ServiceCookie PolicyData Processing Agreement

    Petunia™ is a trademark of Gray Group International LLC. The Petunia name, brand, product design, and content are proprietary. Unauthorized use, imitation, or copying is prohibited.

    Documentation

    REVIEWS

    docs/runbooks/REVIEWS.md
    Docs homeGuidesSupport
    Quick links
    Start here
    How the docs are organized.
    Environment setup
    Configure env + run locally.
    Unified Inbox
    Inbox concepts & behavior.
    Voice setup
    Providers, Twilio, testing.
    Pricing model
    Source-of-truth pricing.
    Operations runbook
    How to operate safely.

    Reviews Management Runbook

    This runbook covers operational procedures for the unified review management system, including multi-platform review aggregation, review response handling, and review analytics.

    Owner: Platform Team Version: 1.0.0 Last Updated: 2025-12-27 Related Docs: GOOGLE_BUSINESS.md, YELP_INTEGRATION.md, FACEBOOK_INSTAGRAM.md


    Table of Contents

    1. Prerequisites
    2. Health Check Commands
    3. Common Failure Modes
    4. Verification Commands
    5. Rollback Steps
    6. Escalation

    Prerequisites

    Required Services

    ServicePurposeHealth Check
    PostgreSQL (Supabase)Review storagepnpm db:health
    Google My Business APIGoogle reviewsGoogle Cloud Console
    Yelp Fusion APIYelp reviewsYelp Developer Portal
    Facebook Graph APIFacebook reviewsMeta Developer Portal
    Redis (Upstash)Sync cachingUpstash Dashboard

    Required Environment Variables

    # Database
    DATABASE_URL="postgresql://..."
    
    # Google Business (for Google reviews)
    NEXT_PUBLIC_GOOGLE_CLIENT_ID="..."
    GOOGLE_CLIENT_SECRET="..."
    
    # Yelp (for Yelp reviews)
    YELP_API_KEY="..."
    
    # Facebook (for Facebook reviews)
    NEXT_PUBLIC_FACEBOOK_APP_ID="..."
    FACEBOOK_CLIENT_SECRET="..."
    

    Key Files

    ComponentFile Path
    Unified Review Servicelib/services/reviews/unifiedReviewService.ts
    Platform Adapterlib/services/reviews/platformAdapter.ts
    Google Review Adapterlib/services/reviews/adapters/googleReviewAdapter.ts
    Yelp Review Adapterlib/services/reviews/adapters/yelpReviewAdapter.ts
    Facebook Review Adapterlib/services/reviews/adapters/facebookReviewAdapter.ts
    BBB Review Adapterlib/services/reviews/adapters/bbbReviewAdapter.ts
    Apple Maps Adapterlib/services/reviews/adapters/appleMapsReviewAdapter.ts
    Nextdoor Review Adapterlib/services/reviews/adapters/nextdoorReviewAdapter.ts
    Review Hookslib/hooks/reviews/

    Supported Platforms

    PlatformAdapterFeatures
    Google BusinessgoogleReviewAdapterFetch, respond, metrics
    YelpyelpReviewAdapterFetch, draft responses
    FacebookfacebookReviewAdapterFetch, respond, ratings
    BBBbbbReviewAdapterFetch, complaints
    Apple MapsappleMapsReviewAdapterFetch only
    NextdoornextdoorReviewAdapterFetch, neighborhood

    Health Check Commands

    Quick Health Check

    # Check review API endpoint
    curl https://your-domain.com/api/reviews/health
    # Expected: {"status":"ok","platforms":["google","yelp","facebook"]}
    

    Platform-Specific Health Checks

    # Google Reviews - check connection
    curl "https://mybusiness.googleapis.com/v4/accounts" \
      -H "Authorization: Bearer $GOOGLE_ACCESS_TOKEN"
    
    # Yelp Reviews - check API
    curl "https://api.yelp.com/v3/businesses/$YELP_BUSINESS_ID/reviews" \
      -H "Authorization: Bearer $YELP_API_KEY"
    
    # Facebook Reviews - check page rating
    curl "https://graph.facebook.com/v18.0/$PAGE_ID/ratings?access_token=$PAGE_ACCESS_TOKEN"
    

    Database Health

    # Check review counts by platform
    psql $DATABASE_URL -c "
    SELECT
      source as platform,
      COUNT(*) as review_count,
      ROUND(AVG(rating), 2) as avg_rating,
      MAX(created_at) as newest_review,
      MAX(synced_at) as last_sync
    FROM \"Review\"
    GROUP BY source
    ORDER BY review_count DESC;
    "
    
    # Check review response rates
    psql $DATABASE_URL -c "
    SELECT
      source as platform,
      COUNT(*) as total_reviews,
      COUNT(response_text) as responded,
      ROUND(
        COUNT(response_text)::numeric / NULLIF(COUNT(*), 0) * 100, 2
      ) as response_rate_pct
    FROM \"Review\"
    WHERE created_at > NOW() - INTERVAL '30 days'
    GROUP BY source;
    "
    
    # Check rating distribution
    psql $DATABASE_URL -c "
    SELECT
      rating,
      COUNT(*) as count,
      ROUND(COUNT(*)::numeric / SUM(COUNT(*)) OVER() * 100, 2) as percentage
    FROM \"Review\"
    WHERE created_at > NOW() - INTERVAL '30 days'
    GROUP BY rating
    ORDER BY rating DESC;
    "
    

    Sync Status Check

    # Check last sync times per client
    psql $DATABASE_URL -c "
    SELECT
      c.id as client_id,
      c.business_name,
      MAX(r.synced_at) as last_review_sync,
      COUNT(r.id) as review_count
    FROM \"Client\" c
    LEFT JOIN \"Review\" r ON c.id = r.client_id
    GROUP BY c.id, c.business_name
    ORDER BY last_review_sync DESC NULLS LAST
    LIMIT 20;
    "
    

    Common Failure Modes

    1. Review Sync Failures

    SymptomLikely CauseResolution
    Reviews not syncingPlatform token expiredRe-authenticate platform
    Missing old reviewsPagination not handledImplement cursor pagination
    Duplicate reviewsDeduplication failedCheck external_id handling
    Sync job timing outToo many reviewsBatch processing

    Diagnostic Commands:

    # Check for sync errors
    psql $DATABASE_URL -c "
    SELECT
      source,
      COUNT(*) as error_count,
      MAX(error_message) as last_error,
      MAX(attempted_at) as last_attempt
    FROM \"ReviewSyncLog\"
    WHERE status = 'failed'
    AND attempted_at > NOW() - INTERVAL '24 hours'
    GROUP BY source;
    "
    
    # Check for duplicate reviews
    psql $DATABASE_URL -c "
    SELECT external_id, source, COUNT(*)
    FROM \"Review\"
    GROUP BY external_id, source
    HAVING COUNT(*) > 1
    LIMIT 20;
    "
    
    # Check sync logs
    grep "review.*sync\|ReviewSync" /var/log/petunia/app.log | tail -30
    

    2. Review Response Issues

    SymptomLikely CauseResolution
    Response not postingAPI permission deniedCheck OAuth scopes
    Response rejectedContent policy violationReview response content
    Response delayedRate limitingImplement backoff
    Wrong review respondedID mismatchVerify review mapping

    Diagnostic Commands:

    # Check failed response attempts
    psql $DATABASE_URL -c "
    SELECT
      r.id,
      r.source,
      r.external_id,
      r.response_status,
      r.response_error,
      r.response_attempted_at
    FROM \"Review\" r
    WHERE r.response_status = 'failed'
    AND r.response_attempted_at > NOW() - INTERVAL '24 hours'
    ORDER BY r.response_attempted_at DESC
    LIMIT 20;
    "
    
    # Check response logs
    grep "review.*response\|ReviewResponse" /var/log/petunia/app.log | tail -30
    

    3. Rating Calculation Issues

    SymptomLikely CauseResolution
    Wrong average ratingStale calculationRecalculate aggregates
    Missing platform ratingsAdapter not returningCheck adapter implementation
    Rating mismatchSync out of dateForce full sync

    Diagnostic Commands:

    # Recalculate average rating
    psql $DATABASE_URL -c "
    SELECT
      client_id,
      source,
      COUNT(*) as review_count,
      ROUND(AVG(rating), 2) as calculated_avg,
      MIN(rating) as min_rating,
      MAX(rating) as max_rating
    FROM \"Review\"
    WHERE client_id = 'your-client-id'
    GROUP BY client_id, source;
    "
    

    4. Platform-Specific Issues

    PlatformCommon IssueResolution
    GoogleOAuth token expiredRe-authenticate via OAuth
    YelpAPI rate limitImplement rate limiting
    FacebookPage permissionsCheck page token scopes
    BBBScraping blockedUpdate user agent/headers

    Verification Commands

    Test Unified Review Service

    import { unifiedReviewService } from '@/lib/services/reviews';
    
    // Fetch reviews for a client
    const result = await unifiedReviewService.getReviewsForClient('client-id', {
      filter: { minRating: 3, platforms: ['google', 'yelp'] },
      sort: { field: 'createdAt', direction: 'desc' },
      limit: 50,
    });
    
    console.log('Reviews:', result.reviews.length);
    console.log('Total:', result.total);
    
    // Get review metrics
    const metrics = await unifiedReviewService.getMetricsForClient('client-id');
    console.log('Average rating:', metrics.averageRating);
    console.log('Response rate:', metrics.responseRate);
    

    Test Platform Adapters

    import { getPlatformAdapter } from '@/lib/services/reviews/platformAdapter';
    
    // Get Google adapter
    const googleAdapter = getPlatformAdapter('google');
    const googleReviews = await googleAdapter.fetchReviews('connection-id');
    console.log('Google reviews:', googleReviews.length);
    
    // Get Yelp adapter
    const yelpAdapter = getPlatformAdapter('yelp');
    const yelpReviews = await yelpAdapter.fetchReviews('connection-id');
    console.log('Yelp reviews:', yelpReviews.length);
    

    Test Review Response

    # Test Google review response
    curl -X PUT "https://mybusiness.googleapis.com/v4/$REVIEW_NAME/reply" \
      -H "Authorization: Bearer $GOOGLE_ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"comment": "Thank you for your feedback!"}'
    

    Run Review Tests

    # Run review service tests
    pnpm test -- --testPathPattern="reviews"
    
    # Run adapter tests
    pnpm test -- --testPathPattern="reviewAdapter"
    

    Rollback Steps

    Disable Review Sync

    # Disable review sync globally
    curl -X POST https://your-domain.com/api/admin/features \
      -H "Authorization: Bearer $ADMIN_TOKEN" \
      -d '{"feature": "reviews.sync.enabled", "enabled": false}'
    

    Disable Review Responses

    # Disable auto-responses
    curl -X POST https://your-domain.com/api/admin/features \
      -H "Authorization: Bearer $ADMIN_TOKEN" \
      -d '{"feature": "reviews.autoResponse.enabled", "enabled": false}'
    

    Reset Sync State

    # Reset last sync timestamp for a client
    psql $DATABASE_URL -c "
    UPDATE \"ReviewSyncState\"
    SET last_sync_at = NULL, sync_cursor = NULL
    WHERE client_id = 'your-client-id';
    "
    
    # Clear sync cache
    curl -X POST "$UPSTASH_REDIS_REST_URL/del/reviews:sync:$CLIENT_ID" \
      -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN"
    

    Rollback Deployment

    # If review changes caused issues
    vercel list --app petunia
    
    # Rollback
    vercel rollback <previous-deployment-url>
    
    # Verify review endpoints
    curl https://your-domain.com/api/reviews/health
    

    Escalation

    When to Escalate

    SeverityCriteriaAction
    P1 - CriticalAll review sync failingImmediate on-call page
    P2 - HighMajor platform sync brokenEscalate within 30 minutes
    P3 - MediumResponse posting issuesEscalate within 2 hours
    P4 - LowMinor display issuesNormal ticket queue

    Escalation Contacts

    RoleContactAvailability
    On-Call EngineerPagerDuty24/7
    Platform TeamSlack #platformBusiness hours
    Google Supportcloud.google.com/supportBusiness hours
    Yelp Partner Supportpartner-support@yelp.comBusiness hours

    Information to Include

    When escalating, include:

    1. Platforms Affected: Which review platforms are failing
    2. Client Impact: Number of affected clients
    3. Error Messages: Specific API errors
    4. Last Successful Sync: When sync last worked
    5. Review Volume: Reviews pending sync

    Monitoring & Alerts

    Key Metrics to Watch

    MetricHealthy RangeAlert Threshold
    Sync success rate> 99%< 95%
    Average ratingVariesSudden drop > 0.5
    Response rate> 80%< 60%
    Review volumeStableDrop > 50%
    Sync latency< 5m> 30m

    Alert Configuration

    • Sentry: Review API errors
    • Slack #reviews-alerts: Sync failures, rating drops
    • PagerDuty: P1/P2 incidents

    Review-Specific Dashboards

    • Google Business Profile: https://business.google.com
    • Yelp for Business: https://biz.yelp.com
    • Facebook Business Suite: https://business.facebook.com

    Debug Findings (Cursor debug.log)

    These notes capture root causes and fixes discovered via Cursor debug.log instrumentation (NDJSON) during MVP hardening.

    2026-01-03 — Reviews error flood (“Unauthorized” / “[object Object]”)

    • Runtime evidence

      • Repeated REVIEWS_FETCH_HTTP for /api/reviews/unified?... with status 401/500
      • Repeated AUTH_ROUTE_NO_SESSION for /api/reviews/unified
    • Root causes

      • Refetch loop from unstable filter/sort identities (inline object literals causing hook churn)
      • Non-string error payloads producing [object Object]
      • Auth/setup failures treated like “real errors” → console spam
    • Fixes applied

      • lib/hooks/reviews/useReviews.ts
        • Stabilized dependency behavior to prevent refetch loops
        • Improved error message extraction
        • Classified auth/setup failures as expected (no console.error spam)
      • lib/hooks/reviews/useReviewMetrics.ts
        • Added needsSetup state to treat 401/403/404/500 as “not configured yet”
      • app/(client)/reviews/page.tsx + components/dashboard/ReviewsTabContent.tsx
        • Show “Connect review platforms” empty state with 0s and clear CTA when needsSetup

    Review Response Best Practices

    Response Templates

    // Positive review (4-5 stars)
    const positiveTemplate = `Thank you so much for the wonderful review, {{reviewer_name}}! We're thrilled to hear about your experience. We look forward to serving you again!`;
    
    // Neutral review (3 stars)
    const neutralTemplate = `Thank you for your feedback, {{reviewer_name}}. We appreciate you taking the time to share your experience. If there's anything we can do to improve, please let us know at {{support_email}}.`;
    
    // Negative review (1-2 stars)
    const negativeTemplate = `We're sorry to hear about your experience, {{reviewer_name}}. This is not the standard we strive for. Please contact us at {{support_email}} so we can make this right.`;
    

    Response Guidelines

    1. Respond quickly: Within 24-48 hours
    2. Personalize: Use reviewer's name when available
    3. Stay professional: Even for negative reviews
    4. Address specifics: Acknowledge specific concerns
    5. Provide next steps: Offer resolution path

    Monitoring Review Trends

    -- Weekly review trends
    SELECT
      DATE_TRUNC('week', created_at) as week,
      COUNT(*) as review_count,
      ROUND(AVG(rating), 2) as avg_rating,
      COUNT(CASE WHEN rating >= 4 THEN 1 END) as positive,
      COUNT(CASE WHEN rating <= 2 THEN 1 END) as negative
    FROM "Review"
    WHERE created_at > NOW() - INTERVAL '12 weeks'
    GROUP BY week
    ORDER BY week DESC;
    

    Last verified: 2025-12-27

    On this page
    Table of ContentsPrerequisitesRequired ServicesRequired Environment VariablesKey FilesSupported PlatformsHealth Check CommandsQuick Health CheckPlatform-Specific Health ChecksDatabase HealthSync Status CheckCommon Failure Modes1. Review Sync Failures2. Review Response Issues3. Rating Calculation Issues4. Platform-Specific IssuesVerification CommandsTest Unified Review ServiceTest Platform AdaptersTest Review ResponseRun Review TestsRollback StepsDisable Review SyncDisable Review ResponsesReset Sync StateRollback DeploymentEscalationWhen to EscalateEscalation ContactsInformation to IncludeMonitoring & AlertsKey Metrics to Watch