Prisma Reviews / Guides / Reviews to Airtable

Send customer reviews to Airtable automatically: upsert by review id

Airtable is where many small teams already track locations, clients and tasks, and a reviews table next to them is useful: filter by rating, group by platform, build an interface for the people who answer. Airtable has no reviews integration, so rows come from its REST API, and the one feature that makes this clean is upsert: one call that creates the row the first time and updates it later. This guide does it in Python with a Prisma webhook (one event per new review on 84 platforms), then shows the backfill and the n8n version.

Shell
pip install flask requests

You need a Prisma API key and webhook secret, an Airtable personal access token with data.records:read and data.records:write on the base, the base id (app…) and the table name or id.

1. The table

Fields, exactly named: Review ID (single line text), Title (single line text), Rating (number), Platform (single select), Author (single line text), Date (date, with time), Location (single line text or link), Text (long text), URL (URL), Replied (checkbox). Airtable creates select options on the fly when typecast is true, so new platforms don’t break the insert.

2. Receive the webhook and upsert the row

Python
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"]
AT = {"Authorization": f"Bearer {os.environ['AIRTABLE_TOKEN']}", "Content-Type": "application/json"}
TABLE = f"https://api.airtable.com/v0/{os.environ['AIRTABLE_BASE']}/{os.environ.get('AIRTABLE_TABLE', 'Reviews')}"

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

def fields(r: dict) -> dict:
    return {"Review ID": r["id"], "Title": r.get("title") or "", "Rating": r.get("rating"), "Platform": r["platform"],
            "Author": r.get("author") or "", "Date": r["created_at"], "Location": r["location_id"],
            "Text": r.get("text") or "", "URL": r.get("url") or "", "Replied": bool(r.get("reply"))}

def upsert(reviews: list[dict]):
    for i in range(0, len(reviews), 10):                      # 10 records per request
        body = {"performUpsert": {"fieldsToMergeOn": ["Review ID"]}, "typecast": True,
                "records": [{"fields": fields(r)} for r in reviews[i:i + 10]]}
        res = requests.patch(TABLE, headers=AT, json=body, timeout=30)
        if res.status_code == 429:
            time.sleep(30); res = requests.patch(TABLE, headers=AT, json=body, timeout=30)
        res.raise_for_status()

@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"] in ("review.created", "review.updated", "reply.published"):
        review = requests.get(f"{PRISMA}/reviews/{event['data']['id']}", headers=PH, timeout=30).json()
        upsert([review])
    return {"ok": True}

Because the merge key is Review ID, the same handler serves three events: review.created inserts the row, review.updated refreshes an edited review, and reply.published flips Replied to true. Redeliveries after a failure (same event, same review id) just update the same row. Airtable allows 5 requests a second per base.

3. Register the webhook

cURL
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", "review.updated", "reply.published"]}'

4. Backfill the history

Python
params, batch = {"limit": 100}, []
while True:
    page = requests.get(f"{PRISMA}/reviews", headers=PH, params=params, timeout=30).json()
    batch.extend(page["data"])
    if not page.get("next_cursor"):
        break
    params["cursor"] = page["next_cursor"]
upsert(batch)   # 10 per request, paced on 429

5. The same with n8n, no code

Prisma Reviews trigger (new review) → Airtable node, operation Upsert (Update or create), matching column Review ID, with the fields above mapped. The Prisma node is verified on n8n Cloud (n8n setup). Zapier and Make: catch hook → Prisma “get review” → Airtable “find or create record” (webhooks in Zapier and Make).

Variants

The same webhook, other tools: HubSpot · Notion · Zendesk · Intercom · Slack (approve replies) · Slack (alerts) · Microsoft Teams · Discord · Google Sheets.

FAQ

Does Airtable have a Google reviews integration?

No. Airtable’s integrations don’t cover review platforms. Rows are created through Airtable’s REST API (PATCH or POST /v0/{baseId}/{table}) by your script, n8n, Zapier or Make, fed by a reviews webhook.

How do I keep Airtable rows from duplicating?

Use Airtable’s upsert: PATCH /v0/{baseId}/{table} with performUpsert and fieldsToMergeOn set to a Review ID field. The same call creates the row the first time and updates it later, for example when the reply is published.

What are Airtable’s API limits?

5 requests per second per base and 10 records per create or update request; a 429 asks you to wait 30 seconds. Batch the backfill in groups of 10 and pace it.

Every review, one Airtable base, no duplicates.

Get a free API key

2 locations free. Test keys return sample reviews, so the table fills today.

Next step: see the 84 review platforms Prisma supports, and compare plans on Prisma Reviews pricing.

Get your API key in a minute

Free Spark plan: 2 locations, no card. Test keys return sample data on all 84 platforms, so you can build before connecting a real one.