> For the complete documentation index, see [llms.txt](https://docs.pullbay.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.pullbay.com/documentation/guides/how-to-guides-for-app-store/how-to-monitor-negative-reviews.md).

# How to Monitor Negative Reviews

Missing a negative review that's damaging your app's reputation is costly. The longer a 1 or 2-star review sits without a response, the more damage it does to your rating. This guide teaches you **how to monitor negative app store reviews** and receive instant alerts via Slack or email whenever users leave low-star feedback. By the end, you'll have an automated system checking for negative reviews every hour.

## Why Negative Review Monitoring Matters

Negative reviews are your early warning system:

* **Catch bugs early**: Identify critical issues before they spread to more users
* **Respond quickly**: Show users you care by acknowledging problems within hours, not days
* **Prevent rating decay**: A single unanswered 1-star review can tank your app's rating
* **Track quality regressions**: Sudden spikes in negative reviews signal a new version issue
* **Competitive intelligence**: Monitor competitor apps to stay ahead of their problems

Let's build an automated monitoring system.

## The Monitoring Approach

The strategy is simple:

1. Poll the Pullbay API every hour (or more frequently)
2. Fetch recent reviews and filter for ratings ≤ 2
3. Compare against reviews you've already seen (using a timestamp watermark)
4. Send an alert (Slack, email, or webhook) when new negative reviews appear

## Step 1: Set Up the API Call for Fresh Reviews

The Pullbay `/v1/app-store/reviews/all` endpoint returns reviews with timestamps. We'll filter for recent ones.

```python
import requests
from datetime import datetime, timedelta

API_KEY = "live_your_api_key_here"
APP_ID = "123456789"
BASE_URL = "https://api.pullbay.com/v1"

# Fetch reviews from the last 24 hours
response = requests.get(
    f"{BASE_URL}/app-store/reviews/all",
    params={"app_id": APP_ID},
    headers={"Authorization": f"Bearer {API_KEY}"}
)

reviews = response.json().get("data", [])
print(f"Fetched {len(reviews)} total reviews")
```

## Step 2: Filter for Low Ratings

Extract only reviews with rating ≤ 2 (1-star and 2-star reviews):

```python
negative_reviews = [r for r in reviews if r.get("rating", 5) <= 2]
print(f"Found {len(negative_reviews)} negative reviews")
```

## Step 3: Track New Reviews Since Last Run

To avoid sending duplicate alerts, store a timestamp file of when you last checked:

```python
import json
import os
from datetime import datetime

STATE_FILE = "review_monitor_state.json"

def load_last_check_time():
    """Load the timestamp of the last check."""
    if os.path.exists(STATE_FILE):
        with open(STATE_FILE, "r") as f:
            state = json.load(f)
            return datetime.fromisoformat(state.get("last_check"))
    return datetime.now() - timedelta(days=1)

def save_last_check_time(timestamp):
    """Save the current check timestamp."""
    with open(STATE_FILE, "w") as f:
        json.dump({"last_check": timestamp.isoformat()}, f)

last_check = load_last_check_time()
new_negative_reviews = [
    r for r in negative_reviews
    if datetime.fromisoformat(r.get("created_at")) > last_check
]

print(f"Found {len(new_negative_reviews)} new negative reviews since last check")
```

## Step 4: Send Slack Notification

Use a Slack incoming webhook to post alerts directly to a channel:

```python
import requests

SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"

def send_slack_alert(reviews):
    """Send a Slack notification for negative reviews."""
    if not reviews:
        return

    message = {
        "text": f":warning: {len(reviews)} new negative reviews detected!",
        "blocks": [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": f"*{len(reviews)} New Negative Reviews* :warning:\n\n"
                }
            }
        ]
    }

    # Add each review to the message
    for review in reviews[:5]:  # Show first 5
        message["blocks"].append({
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": f"*{review.get('rating', '?')}-star* | {review.get('author', 'Anonymous')}\n"
                        f"_{review.get('title', 'No title')}_\n"
                        f"{review.get('content', '')[:200]}"
            }
        })

    requests.post(SLACK_WEBHOOK, json=message)
    print(f"✓ Sent Slack alert for {len(reviews)} reviews")
```

## Full Python Script: Automated Negative Review Monitor

Here's a complete script that fetches reviews, filters for new negatives, and posts to Slack:

```python
import requests
import json
import os
from datetime import datetime, timedelta

# Configuration
API_KEY = "live_your_api_key_here"
APP_ID = "123456789"
BASE_URL = "https://api.pullbay.com/v1"
SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
STATE_FILE = "review_monitor_state.json"

def load_last_check_time():
    """Load the timestamp of the last check."""
    if os.path.exists(STATE_FILE):
        try:
            with open(STATE_FILE, "r") as f:
                state = json.load(f)
                return datetime.fromisoformat(state.get("last_check"))
        except:
            pass
    # Default to 24 hours ago
    return datetime.now() - timedelta(days=1)

def save_last_check_time(timestamp):
    """Save the current check timestamp."""
    with open(STATE_FILE, "w") as f:
        json.dump({"last_check": timestamp.isoformat()}, f)

def fetch_reviews():
    """Fetch all reviews from Pullbay."""
    response = requests.get(
        f"{BASE_URL}/app-store/reviews/all",
        params={"app_id": APP_ID},
        headers={"Authorization": f"Bearer {API_KEY}"}
    )

    if response.status_code != 200:
        print(f"Error fetching reviews: {response.status_code}")
        return []

    return response.json().get("data", [])

def filter_negative_reviews(reviews, since):
    """Filter for new negative reviews (rating ≤ 2) since last check."""
    negative = []
    for review in reviews:
        rating = review.get("rating", 5)
        created = datetime.fromisoformat(review.get("date", ""))
        if rating <= 2 and created > since:
            negative.append(review)
    return negative

def send_slack_alert(reviews):
    """Send a Slack message for each new negative review."""
    if not reviews:
        return

    # Build Slack message
    message = {
        "text": f":warning: {len(reviews)} new negative review(s) on your app",
        "blocks": [
            {
                "type": "header",
                "text": {
                    "type": "plain_text",
                    "text": f":warning: {len(reviews)} New Negative Review(s)"
                }
            }
        ]
    }

    # Add up to 5 reviews
    for review in reviews[:5]:
        message["blocks"].append({
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": f"*{review.get('rating', '?')}★* from {review.get('author', 'Anonymous')} "
                        f"({review.get('id', '')[:6]})\n"
                        f"*{review.get('title', 'No title')}*\n"
                        f"{review.get('content', '')[:300]}\n"
                        f"_v{review.get('version', '?')} · {review.get('date', '')}_"
            }
        })

    if len(reviews) > 5:
        message["blocks"].append({
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": f"...and {len(reviews) - 5} more"
            }
        })

    response = requests.post(SLACK_WEBHOOK, json=message)
    if response.status_code == 200:
        print(f"✓ Slack alert sent for {len(reviews)} reviews")
    else:
        print(f"Failed to send Slack alert: {response.status_code}")

def main():
    """Main monitoring loop."""
    print(f"Checking for negative reviews on app {APP_ID}...")

    last_check = load_last_check_time()
    print(f"Last check: {last_check}")

    # Fetch and filter
    reviews = fetch_reviews()
    negative = filter_negative_reviews(reviews, last_check)

    if negative:
        print(f"Found {len(negative)} new negative reviews")
        send_slack_alert(negative)
    else:
        print("No new negative reviews")

    # Update state
    save_last_check_time(datetime.now())

if __name__ == "__main__":
    main()
```

## Running as a Cron Job

To run this script every hour, add it to your crontab:

```bash
# Edit crontab
crontab -e

# Add this line to run every hour
0 * * * * /usr/bin/python3 /path/to/review_monitor.py >> /var/log/review_monitor.log 2>&1
```

For other intervals:

* **Every 30 minutes**: `*/30 * * * *`
* **Every 6 hours**: `0 */6 * * *`
* **Every morning at 9 AM**: `0 9 * * *`

## Alternative: Email Alerts

If you prefer email instead of Slack, use Python's built-in `smtplib`:

```python
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

def send_email_alert(reviews):
    """Send email alert for negative reviews."""
    sender_email = "alerts@example.com"
    sender_password = "your_password"
    recipient_email = "team@example.com"

    message = MIMEMultipart("alternative")
    message["Subject"] = f"{len(reviews)} New Negative Reviews - Action Required"
    message["From"] = sender_email
    message["To"] = recipient_email

    # Create HTML body
    html = f"<h2>{len(reviews)} New Negative Reviews</h2>\n<ul>\n"
    for review in reviews:
        html += f"<li><strong>{review.get('rating')}★</strong> - {review.get('title', '')}<br/>"
        html += f"{review.get('content', '')}<br/><em>by {review.get('author', '')}</em></li>\n"
    html += "</ul>"

    part = MIMEText(html, "html")
    message.attach(part)

    with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
        server.login(sender_email, sender_password)
        server.sendmail(sender_email, recipient_email, message.as_string())

    print("✓ Email alert sent")
```

## Advanced: Alert on Rating Drops

Beyond individual reviews, monitor your overall rating trend:

```python
def check_rating_drop(reviews):
    """Check if average rating has dropped significantly."""
    if len(reviews) < 10:
        return False

    recent = [r.get("rating", 3) for r in reviews[:50]]
    avg_rating = sum(recent) / len(recent)

    # Load previous average
    try:
        with open("rating_history.json", "r") as f:
            history = json.load(f)
            prev_avg = history.get("avg_rating", avg_rating)
    except:
        prev_avg = avg_rating

    # Save current
    with open("rating_history.json", "w") as f:
        json.dump({"avg_rating": avg_rating}, f)

    # Alert if drop > 0.5 stars
    if prev_avg - avg_rating > 0.5:
        print(f"⚠️ Rating dropped from {prev_avg:.1f} to {avg_rating:.1f}")
        return True

    return False
```

## Troubleshooting

| Issue                  | Cause                                | Solution                                                                                                   |
| ---------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
| **Duplicate alerts**   | State file lost or state logic issue | Verify `review_monitor_state.json` exists and is being updated after each run                              |
| **No alerts received** | Slack webhook invalid                | Test webhook: `curl -X POST -H 'Content-type: application/json' --data '{"text":"test"}' YOUR_WEBHOOK_URL` |
| **Rate limit errors**  | Checking too frequently              | If polling every 5 minutes, upgrade your plan; see the dashboard for current limits                        |
| **Missing reviews**    | API not returning all reviews        | Verify the app ID is correct and the app has reviews on the App Store                                      |

## Best Practices

* **Adjust frequency**: Free plan = hourly checks; paid plans can go more frequent
* **Monitor multiple apps**: Run separate monitor instances per app or add a loop for a portfolio
* **Set alert thresholds**: Maybe only alert for 1-star reviews, not 2-star
* **Assign owners**: Use Slack channels to route alerts by team

By implementing review monitoring, you'll catch user issues faster and maintain a healthier app rating.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.pullbay.com/documentation/guides/how-to-guides-for-app-store/how-to-monitor-negative-reviews.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
