Prisma Reviews / Guides / Reviews to HubSpot
Send customer reviews to HubSpot automatically: notes on the company record
HubSpot has no integration that brings Google, App Store, Trustpilot or Yelp reviews into the CRM, and its Feedback Submissions object is read-only through the API. What works is to create a note on the company (or contact) record for each new review, with the rating, the text and the link to answer. This guide does it in about 40 lines of Python: a Prisma webhook tells you a review arrived, you fetch it, and you create the note with HubSpot’s CRM API. The same flow in n8n, without code, is at the end. For a multi-location business the company record is the location’s customer; for an agency it’s each client.
pip install flask requestsYou need a Prisma API key, a Prisma webhook secret (shown once when you create the webhook), and a HubSpot private app token (Settings → Integrations → Private apps) with the crm.objects.notes.write and crm.objects.companies.read scopes.
1. Map locations to HubSpot companies
Keep a small table from Prisma location_id to HubSpot company id. The simplest place is a dict; a real integration stores it with the connection.
COMPANY_FOR_LOCATION = {
"loc_01KJ8…": "1234567890", # Prisma location → HubSpot company id
}2. Receive the webhook, verify it, fetch the review
import hashlib, hmac, os, time, requests
from flask import Flask, request, abort
app = Flask(__name__)
PRISMA = "https://api.prismareviews.com/v1"
PH = {"Authorization": f"Bearer {os.environ['PRISMA_API_KEY']}"}
SECRET = os.environ["PRISMA_WEBHOOK_SECRET"]
def verify(raw: bytes, header: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
expected = hmac.new(SECRET.encode(), f"{parts['t']}.".encode() + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"]) and abs(time.time() - int(parts["t"])) < 300
@app.post("/webhooks/prisma")
def prisma_webhook():
if not verify(request.get_data(), request.headers.get("Prisma-Signature", "")):
abort(400)
event = request.get_json()
if event["type"] != "review.created":
return {"ok": True}
review = requests.get(f"{PRISMA}/reviews/{event['data']['id']}", headers=PH, timeout=30).json()
company = COMPANY_FOR_LOCATION.get(review["location_id"])
if company:
create_note(company, review)
return {"ok": True}The review.created payload carries only ids (id, location_id, platform), so the fetch is where the text comes from. The signature is t=<unix>,v1=<HMAC-SHA256 of "t.body">; the full check is in the webhooks reference.
3. Create the note on the company
HS = {"Authorization": f"Bearer {os.environ['HUBSPOT_TOKEN']}", "Content-Type": "application/json"}
def create_note(company_id: str, r: dict):
stars = "★" * (r["rating"] or 0) + "☆" * (5 - (r["rating"] or 0))
body = (f"<p><strong>{r['platform']} review, {stars}</strong> by {r.get('author') or 'a customer'}</p>"
f"<p>{(r.get('title') or '')}</p><p>{(r.get('text') or '')}</p>"
f"<p><a href=\"{r.get('url') or ''}\">Answer on the platform</a></p>")
payload = {
"properties": {"hs_timestamp": r["created_at"], "hs_note_body": body},
"associations": [{"to": {"id": company_id}, "types": [{"associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 190}]}],
}
res = requests.post("https://api.hubapi.com/crm/v3/objects/notes", headers=HS, json=payload, timeout=30)
res.raise_for_status()
return res.json()["id"]associationTypeId 190 is HubSpot’s note-to-company association; use 202 for note-to-contact. hs_timestamp takes the review’s date so the note sits at the right point of the timeline. HubSpot’s private-app limit is 100 requests per 10 seconds, far above what reviews produce.
4. Register the webhook
curl -X POST https://api.prismareviews.com/v1/webhooks \
-H "Authorization: Bearer $PRISMA_API_KEY" -H "Content-Type: application/json" \
-d '{"url": "https://yourapp.example.com/webhooks/prisma", "events": ["review.created"]}'The response includes the secret for PRISMA_WEBHOOK_SECRET. Deliveries retry with backoff (30 s doubling to 1 h) when your endpoint doesn’t answer 2xx, with the same event id, so an occasional restart loses nothing.
5. The same with n8n, no code
Prisma Reviews trigger (new review) → HubSpot node, resource Engagement, type Note, with the company association and the rating, text and URL in the body. The Prisma node is verified on n8n Cloud (n8n setup); the mapping from location to company is a Code node or a Google Sheet lookup.
Variants
- Only bad reviews: check
review["rating"] <= 2before creating the note, and create a HubSpot task on the owner instead (/crm/v3/objects/tasks, same association pattern) so it shows up in their queue. - Reviews as a custom object (Enterprise): create a
reviewsobject once with rating, platform, text, url and date properties, thenPOST /crm/v3/objects/p_reviewsper review and associate it to the company. Reports and workflows can then filter by rating and platform. - Backfill:
GET /v1/reviews?location_id=…&limit=100with the cursor, samecreate_noteper review, newest first.
The same webhook, other tools: Notion · Airtable · Zendesk · Intercom · Slack (approve replies) · Slack (alerts) · Microsoft Teams · Discord · Google Sheets.
FAQ
Does HubSpot have a native Google reviews integration?
No. HubSpot’s own feedback surveys are its Feedback Submissions object, which the API only reads. External reviews reach HubSpot through the CRM API: as notes, custom-object records or timeline events created by your integration.
Which HubSpot object should a review become?
A note associated to the company (or contact) is the simplest and works on every HubSpot tier: it shows in the record’s timeline with the rating, text and link. A custom object named Reviews is better for reporting but needs Enterprise.
How does my server know a new review arrived?
Register a webhook with POST /v1/webhooks for review.created. Prisma sends the review’s id, location and platform, signed with Prisma-Signature; your endpoint fetches the full review with GET /v1/reviews/{id} and creates the HubSpot note.
Every review, in the CRM, the minute it lands.
2 locations free. Test keys return sample reviews, so the webhook fires today.