Turn App Store and Google Play reviews into Jira issues
Webhooks: Enterprise plan · MCP: every plan · 14-day free trial
What works today
Option 1: webhook destination and a receiver
- 1
Deploy the receiver
Host the code below anywhere that serves HTTPS (a serverless function is enough). It needs a Jira token with permission to create issues. - 2
Add a webhook destination
In Review Expert open Automation → Manage destinations, choose Webhook and paste your receiver URL. Copy the signing secret shown once after you save it into the receiver'sREVIEWEXPERT_WEBHOOK_SECRET. - 3
Set filters and send a test
For bug triage, a common filter is ratings 1 to 2 or keywords such as "crash" and "login". Send a test notification (it has "test": true) to check the Jira issue is created.
X-ReviewExpert-Timestamp: <unix seconds>
X-ReviewExpert-Signature: v1=<hex HMAC-SHA256(secret, "<timestamp>.<raw body>")>
X-ReviewExpert-Event: review.created | review.negative | review.positive
X-ReviewExpert-Delivery: <stable id per destination and review, repeated on retries>{
"delivery_id": "…",
"event": "review.negative",
"events": ["review.created", "review.negative"],
"created_at": "2026-10-13T08:00:00.000Z",
"test": false,
"review": {
"id": "3f9c2b1e-…",
"rating": 2,
"title": "Sync broke",
"content": "Sync stopped working after the latest update.",
"author_name": "Alex M.",
"posted_at": "2026-10-13T07:51:00.000Z",
"sentiment": "negative",
"app_version": "4.2.0",
"country": "US",
"url": "https://…"
},
"property": { "id": "8a71d0c4-…", "name": "Acme Notes", "type": "google-play", "external_id": "com.acme.notes", "icon_url": null, "store_url": "https://…" },
"links": { "review_url": "https://review.expert/admin/…" }
}import crypto from "node:crypto";
import http from "node:http";
const SECRET = process.env.REVIEWEXPERT_WEBHOOK_SECRET;
const seen = new Set(); // use a database in production
function verify(raw, headers) {
const ts = Number(headers["x-reviewexpert-timestamp"]);
const sig = String(headers["x-reviewexpert-signature"] || "");
if (!Number.isInteger(ts) || Math.abs(Date.now() / 1000 - ts) > 300) return false;
const expected = "v1=" + crypto.createHmac("sha256", SECRET).update(ts + "." + raw).digest("hex");
return sig.split(",").some((s) => s.trim().length === expected.length &&
crypto.timingSafeEqual(Buffer.from(s.trim()), Buffer.from(expected)));
}
// Jira Cloud REST API v3. JIRA_TOKEN is an Atlassian API token for JIRA_EMAIL.
async function createIssue(summary, text) {
const auth = Buffer.from(`${process.env.JIRA_EMAIL}:${process.env.JIRA_TOKEN}`).toString("base64");
const res = await fetch(`https://${process.env.JIRA_SITE}.atlassian.net/rest/api/3/issue`, {
method: "POST",
headers: { Authorization: `Basic ${auth}`, "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify({
fields: {
project: { key: process.env.JIRA_PROJECT }, // e.g. "MOB"
issuetype: { name: "Bug" },
summary: summary.slice(0, 250),
labels: ["app-review"],
description: {
type: "doc",
version: 1,
content: [{ type: "paragraph", content: [{ type: "text", text }] }],
},
},
}),
});
return res.ok;
}
http.createServer(async (req, res) => {
let raw = "";
for await (const chunk of req) raw += chunk;
if (req.method !== "POST" || !verify(raw, req.headers)) return res.writeHead(401).end();
const delivery = req.headers["x-reviewexpert-delivery"];
if (seen.has(delivery)) return res.writeHead(200).end();
const { review, property, links } = JSON.parse(raw);
const title = `[${property.name}] ${review.rating}★ ${review.title ?? (review.content ?? "").slice(0, 60)}`;
const body = [
review.content ?? "(no text)",
"",
`Rating: ${review.rating}/5 · Version: ${review.app_version ?? "n/a"} · Country: ${review.country ?? "n/a"}`,
`Review in Review Expert: ${links.review_url}`,
].join("\n");
const ok = await createIssue(title, body);
if (ok) seen.add(delivery);
res.writeHead(ok ? 200 : 502).end(); // a non-2xx makes Review Expert retry
}).listen(process.env.PORT || 8080);
Set JIRA_SITE (the part before .atlassian.net), JIRA_EMAIL, JIRA_TOKEN (an Atlassian API token) and JIRA_PROJECT. Change issuetype if your project has no “Bug” type. The description uses Atlassian Document Format, which Jira Cloud's v3 API requires.
Option 2: an agent with MCP
The Review Expert MCP server is live on every plan. Connect it to an agent (for example in Claude, ChatGPT, Cursor or VS Code) that also has a Jira connection, then ask something like: “Find 1–2 star Google Play reviews from the last 7 days that mention crashes, group them by problem and open one Jira issue per problem with links to the reviews.”
The agent uses tools such as search_reviews, get_app_reviews and get_review to read reviews; creating the Jira issue is done by the agent's own Jira tooling, not by Review Expert. See the tool reference.
Good practice
Plans
| Destination | Starter | Pro | Enterprise |
|---|---|---|---|
| Price (monthly / billed annually) | $49 / $39 per month | $149 / $119 per month | $399 / $319 per month |
| Connected apps | 2 | 4 | 10 |
| Team members | 5 | 15 | Unlimited |
| Slack | ✓ | ✓ | ✓ |
| Linear | ✓ | ✓ | ✓ |
| Microsoft Teams | ✗ | ✓ | ✓ |
| Zendesk | ✗ | ✓ | ✓ |
| Webhooks | ✗ | ✗ | ✓ |
| Zapier | ✗ | Coming soon | Coming soon |
| Filters: min rating, max rating, keywords | ✓ | ✓ | ✓ |
