Automating iOS App Review Monitoring with Google Cloud Functions and Slack Notifications

Introduction

Monitoring user feedback is essential for improving mobile applications. App Store reviews offer direct insights from users, helping developers address issues and enhance their app’s performance. In this post, we’ll walk through automating the retrieval of iOS App Store reviews and posting them to a Slack channel using the App Store Connect API and Node.js.

This automation ensures real-time updates for your team, allowing them to respond quickly to feedback and improve user satisfaction.

Requirements

Before diving into the implementation, ensure you have the following:

  1. App Store Connect API Access

    • You need access to Apple’s App Store Connect API to fetch customer reviews.

    • Set up an API key from your App Store Connect account and obtain the issuer ID, key ID, and private key.

    • Documentation: Apple Developer

  2. Slack Incoming Webhook URL

    • Set up an incoming webhook in Slack to send review notifications.

  3. Node.js Environment

    • Ensure you have Node.js installed and a project set up to run JavaScript scripts.

Implementation

Install Dependencies

To interact with App Store Connect and send notifications, install the required packages:

npm install axios jsonwebtoken
  • axios: For making HTTP requests.

  • jsonwebtoken: For generating authentication tokens for the App Store Connect API.

Fetching iOS App Reviews

Create a script that authenticates with the App Store Connect API and retrieves the latest app reviews.

const jwt = require('jsonwebtoken');
const axios = require('axios');
const { getConfig } = require('../config');

const SLACK_WEBHOOK_URL = '';

// Retrieve iOS credentials dynamically
async function getIosCredentials() {
  const config = getConfig();
  try {
    const iosConfig = JSON.parse(config.appStore.iosReviewConfig);
    return {
      issuerId: iosConfig.ISSUER_ID,
      keyId: iosConfig.KEY_ID,
      privateKey: iosConfig.PRIVATE_KEY_PATH,
      appId: iosConfig.APP_ID,
    };
  } catch (error) {
    throw new Error('❌ Failed to parse iOS review configuration: ' + error.message);
  }
}

// Create a JWT token for App Store Connect
async function createIosJwt() {
  const { issuerId, keyId, privateKey, appId } = await getIosCredentials();

  const payload = {
    iss: issuerId,
    iat: Math.floor(Date.now() / 1000), // Issued at (current time)
    exp: Math.floor(Date.now() / 1000) + 1200, // Expires in 20 minutes
    aud: 'appstoreconnect-v1',
    scope: [`GET /v1/apps/${appId}/customerReviews`],
  };

  return jwt.sign(payload, privateKey, {
    algorithm: 'ES256',
    header: {
      alg: 'ES256',
      kid: keyId,
      typ: 'JWT',
    },
  });
}

// Fetch recent iOS reviews from App Store Connect
async function fetchRecentIosReviews() {
  try {
    const token = await createIosJwt();
    const { appId } = await getIosCredentials();

    console.log('✅ Generated JWT:', token);

    // Fetch customer reviews
    const response = await axios.get(`https://api.appstoreconnect.apple.com/v1/apps/${appId}/customerReviews`, {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });

    const reviews = response.data.data;

    const now = new Date();
    const twoDaysAgo = new Date(now.setDate(now.getDate() - 1));

    // Filter reviews from the last 2 days
    const recentReviews = reviews.filter(review => {
      const reviewDate = new Date(review.attributes.createdDate);
      return reviewDate >= twoDaysAgo;
    });

    const formattedReviews = recentReviews.map(review => {
      const userComment = review.attributes;
      const reviewDateTime = new Date(userComment.createdDate).toLocaleString('en-GB');

      return {
        text: `*New review for iOS SkyProtect App*\n` +
              `*Rating:* ${userComment.rating} :star:\n` +
              `*Title:* ${userComment.title}\n` +
              `*Comment:* ${userComment.body}\n` +
              `*Reviewer:* ${userComment.reviewerNickname}\n` +
              `*Date:* ${reviewDateTime}\n`,
      };
    });

    await sendToSlack(formattedReviews);

  } catch (error) {
    console.error('❌ Error fetching iOS reviews:', error.response ? error.response.data : error.message);
  }
}

// Send reviews to Slack
async function sendToSlack(reviews) {
  for (const review of reviews) {
    const message = { text: review.text };
    try {
      await axios.post(SLACK_WEBHOOK_URL, message);
      console.log('✅ Sent review to Slack');
    } catch (error) {
      console.error('❌ Error sending to Slack:', error.message);
    }
  }
}

module.exports = {
  fetchRecentIosReviews,
  sendToSlack,
};

How It Works

  • generateJWT(): Generates a JSON Web Token (JWT) for authentication.

  • fetchRecentReviews(): Fetches customer reviews from App Store Connect.

  • sendToSlack(): Sends review details to a specified Slack channel.

Deployment

In the perfect world this will be deployed as a Google Cloud Function and you can use the cron scheduler to enqueue it.

Conclusion

Automating app review monitoring ensures your team stays informed and can respond to user feedback quickly. By leveraging Apple’s App Store Connect API and Slack, you streamline the process and eliminate the need for manual review checks.

You can extend this setup by:

  • Filtering reviews based on star ratings.

  • Automatically responding to certain types of feedback.

  • Logging review trends over time.

Would you like to see an implementation for real-time notifications using other platforms like Telegram or Microsoft Teams? Let me know in the comments!

Previous
Previous

The Importance of Real-Time Monitoring and Service Level Indicators (SLIs) for App Performance

Next
Next

Autumn