How to ask for Google reviews over iMessage, without review gating
With ZeroSettle, a local business asks for Google reviews over iMessage in two steps: a question with no link in the customer's existing thread ("How did Maria and the team do today?"), then the Google review link once the customer replies, when their iPhone shows it as a link they can tap. The link goes to everyone who replies, happy or not, because Google's review policy forbids asking only happy customers and the FTC has said this "review gating" can violate the FTC Act. Customers who already have a thread with you from booking or reminders don't count toward the Pro plan's 5 new contacts a day.
Key takeaways
- What it is: a review request in two steps, a question with no link and then your Google review link, sent from your business's own iMessage number through ZeroSettle's REST API. Replies arrive on a signed webhook.
- The rule: everyone who answers gets the same link. Your code sends it without looking at what the customer said.
- Price: Pro is $49/month and starts with one number, with unlimited messages with people who message you first. Growth, for several numbers working as one, is from $599/mo. There are no per-message fees.
- Main limit: asking a customer your number has never texted uses one of Pro's 5 new contacts for the day. Customers already in a thread don't count.
- What it does not do: there is no SMS fallback for Android customers, and the API doesn't schedule sends. Your CRM or POS decides when to ask.
Who uses iMessage for review requests?
iMessage review requests fit local service businesses that depend on Google reviews and already text their customers: cleaning companies, contractors, salons, and restaurants that take bookings by text. ZeroSettle is an iMessage API: a real phone number on real iMessage, running on Apple hardware we operate for you, that your code texts customers from and hears back on through a bearer token and signed webhooks.
The problem: an emailed request competes with the whole inbox, and a review link texted from an unknown number can look like phishing and, on an iPhone, can't be tapped. A question in a thread the customer already knows avoids both. ZeroSettle is a poor fit if most of your customers use Android (see when ZeroSettle is not a fit). This is one of the 50 ways businesses use iMessage.
How iMessage review requests work, step by step
A ZeroSettle review request is one question with no link when the job closes, then one link after the customer answers. Every answer gets the same link.
- A job closes in your CRM or POS. Between 8 am and 9 pm in the customer's time zone, your code sends a plain question people answer through
POST /api/imessage/send: "How did Maria and the team do today?" Outside those hours, the question waits until 8 am. - The question has no link, because an iPhone shows a link as plain text until the customer has replied to your number or saved it. A link sent to someone who has never texted your number still goes out, and the API adds
warning.code: "link_not_tappable"to the response (why links in a first iMessage aren't tappable). - The customer answers. ZeroSettle posts a signed
message.receivedevent to your webhook about 1–2 seconds after the line receives the text. A tapback on the question arrives asreaction.received. - Your code sends the Google review link to everyone who answered, with the same text. A 👎 gets exactly what a ❤️ gets.
- Every answer also goes to the owner, who handles complaints from the ZeroSettle console. A complaint never holds the link back.
- A ❤️ on the link itself arrives as
reaction.received. Count it as engagement, not as proof of a review.
Customers who opted in when they booked usually already have a thread from appointment reminders or on-the-way texts, so the question uses none of your new-contact allowance.
Why asking first is not review gating
Asking first is fine; filtering who gets the link by their answer is not. The FTC describes gating like this:
Review gating occurs when a business asks past purchasers to provide feedback on a product and then invites only those who provide positive feedback to post online reviews on one or more websites.
Federal Trade Commission, final rule on consumer reviews (2024), quoting its 2023 proposal
The 2024 rule doesn't ban gating by name, but the Commission wrote that it "can nonetheless violate Section 5 of the FTC Act", and its guide for marketers says "Don't ask for reviews only from customers you think will leave positive ones." Google's policy says merchants may not "selectively solicit positive reviews from customers". So the code that sends the link never reads the answer.
Example conversation: review requests
The same flow with a happy customer and an unhappy one. Both get the same link.
- Thu 4:12 PM
- Business: Hi Rosa, it's Sparkle Home Cleaning. Thanks for having us today! How did Maria and the team do?
- Customer: honestly the house looks amazing
- Business: Thank you for telling us, Rosa. If you have 30 seconds, an honest Google review helps a small team like ours: reviews.example.com/sparkle❤️
- Customer: done!
- Thu 5:40 PM
- Business: Hi Dan, it's Sparkle Home Cleaning. Thanks for having us today! How did Maria and the team do?
- Customer: kitchen looks great but the upstairs bathroom got skipped
- Business: Thank you for telling us, Dan. If you have 30 seconds, an honest Google review helps a small team like ours: reviews.example.com/sparkle
- Business: This is Jen, the owner. Sorry about the bathroom. Can we come back tomorrow morning to finish it?
- Customer: tomorrow at 9 works
Setup: a job-close trigger and a reply router
A ZeroSettle review flow needs two pieces of code: a function your CRM or POS calls when a job closes, and a webhook receiver that sends the link after any answer, text or tapback. Copy your review link from your Google Business Profile: Read Reviews, then Get more reviews, then the copy icon (Google's steps).
# reviews.py: run with uvicorn reviews:app --port 8100
import hashlib, hmac, json, os, time
from datetime import datetime
from zoneinfo import ZoneInfo
import requests
from fastapi import BackgroundTasks, FastAPI, Request, Response
BASE = "https://portal.zerosettle.io"
TOKEN, SECRET = os.environ["ZEROSETTLE_TOKEN"], os.environ["ZEROSETTLE_WEBHOOK_SECRET"]
LINK = os.environ["GOOGLE_REVIEW_LINK"]
app, asked, seen = FastAPI(), {}, set() # asked: E.164 phone -> visit (use your database)
def send(to, text):
return requests.post(f"{BASE}/api/imessage/send", json={"to": to, "text": text},
headers={"Authorization": f"Bearer {TOKEN}"}, timeout=90).json()
def on_job_closed(visit): # call this from your CRM or POS
if not 8 <= datetime.now(ZoneInfo(visit["tz"])).hour < 21:
return queue_for_8am(visit) # 8 am to 9 pm, the customer's time zone
asked[visit["phone"]] = visit
r = send(visit["phone"], f"Hi {visit['name']}, it's Sparkle Home Cleaning. Thanks for having us "
f"today! How did {visit['lead']} and the team do?")
if not r.get("ok"): # e.g. 429 new_contact_limit: nothing was sent,
asked.pop(visit["phone"], None) # so a later text from them gets no link
def on_answer(m): # a text or a tapback, whatever it says
visit = asked.pop(m["handle"], None) # one link per visit
if visit:
notify_owner(visit, m)
send(m["handle"], f"Thank you for telling us, {visit['name']}. If you have 30 seconds, "
f"an honest Google review helps a small team like ours: {LINK}")
def verify(header, raw): # from the ZeroSettle docs
try:
parts = dict(p.split("=", 1) for p in header.split(","))
t, v1 = int(parts["t"]), parts["v1"]
except Exception:
return False
if abs(time.time() - t) > 300:
return False
expected = hmac.new(SECRET.encode(), f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)
@app.post("/webhook")
async def webhook(request: Request, tasks: BackgroundTasks):
raw = await request.body()
if not verify(request.headers.get("X-ZeroSettle-Signature", ""), raw):
return Response(status_code=401)
ev = json.loads(raw)
if ev["event"] in ("message.received", "reaction.received"):
key = (ev["message"]["id"], ev["event"]) # retries repeat the id and event
if key not in seen:
seen.add(key)
tasks.add_task(on_answer, ev["message"])
return Response(status_code=200)
def notify_owner(visit, m): print("answer:", m["handle"], m.get("text") or m.get("reaction"))
def queue_for_8am(visit): print("hold until 8 am:", visit["phone"]) # your job queue
Register the receiver with PUT /api/imessage/webhook and store the signing secret it returns, because ZeroSettle shows it only once. The webhook steps in the ZeroSettle docs cover retries and testing.
Limits and consent for review requests
A review request uses ZeroSettle's new-contact allowance only when the question goes to a customer your number has never texted, and then only once. The link and every tapback never count.
| Message | Counts as a new contact? |
|---|---|
| Question to a customer who texted your number first | No, never limited |
| Question to a customer you already text (reminders, on-the-way texts) | No, an ongoing conversation |
| Question to a customer your number has never texted | Yes, once: 5 a day per number |
| Review link after they answer | No |
| Tapbacks, either way | Never |
The day runs from 3 AM to 3 AM Eastern. Growth allows 50 new contacts a day and 3 per 15 minutes, per number.
For example, a cleaning company closes 14 jobs on a Thursday. Twelve customers got yesterday's reminder from the same number. Two booked with a phone call, agreed to texts, and haven't been texted yet. The twelve don't count, and the two use 2 of Pro's 5 for the day.
Ask once per visit, with no nudges. Both plans refuse a third unanswered message to a contact with 403 awaiting_reply, and the sample goes further: it sends the question once per closed job, never follows up, and asked.pop allows one link per visit.
Consent comes first
Text only existing customers who gave you their number for texts about their visit, keep the record, and say who you are. A review request may be treated as promotional, so send it between 8 am and 9 pm in the customer's time zone. ZeroSettle enforces STOP across every number on your account; see iMessage and TCPA consent. This isn't legal advice; rules vary by place and purpose.
When ZeroSettle is not a fit for review requests
ZeroSettle is the wrong tool for review requests when:
- You want to send review links only to happy customers. That is review gating. Google's policy prohibits it, and the FTC has said it can violate the FTC Act (this isn't legal advice).
- You want to offer a discount for a review. Google prohibits incentives for reviews, and the FTC's 2024 rule bans incentives conditioned on a review's sentiment.
- You want one text to every customer from the past year. They didn't opt in to this, unsolicited volume gets a number filtered, and on Pro each customer your number has never texted uses one of 5 new contacts a day.
- Most of your customers use Android. There is no SMS fallback, so ask those customers by email.
- Your review platform's own requests already bring in the reviews you need. A second ask over iMessage would mean two requests for one visit.
To score service rather than collect reviews, see customer feedback over iMessage.
How much does iMessage for review requests cost?
Review requests cost only the plan price. ZeroSettle has no per-message fees, and messages are unlimited (but counted).
| Plan | Price | Fits |
|---|---|---|
| Pro | $49/month | One number to start, first messages to 5 new contacts a day, unlimited messages with people who message you first, self-serve, no setup fee |
| Growth | From $599/mo | Several numbers working as one, set up after a demo |
Both plans have a 30-day money-back guarantee on the subscription. See the plans on zerosettle.io for full details.
Frequently asked questions
Does a ❤️ tapback make the review link tappable?
That isn't documented. ZeroSettle reports the tapback as reaction.received, but nothing documents whether iOS counts a tapback alone as a reply when it decides if a link is tappable. When the link matters, ask a question that invites a short text reply.
Can the review request name the technician?
Yes, in the question ("How did Maria do?"). Don't ask the customer to mention Maria in the review, though. Google's policy says merchants shouldn't request specific review content, such as naming a staff member.
What happens if a customer answers with STOP?
A text with the word STOP, or a clear request like "take me off your list", opts the customer out of every number on your account. ZeroSettle then refuses the review link with 403 recipient_opted_out and sends nothing. Only texting START opts them back in.
Sources
- ZeroSettle iMessage API docs: "Send a message", "Links in a first message", "Reactions (tapbacks)", "Webhooks, step by step" and "Errors, limits, and compliance". Checked October 12, 2026.
- Prohibited and restricted content, Maps User Generated Content Policy Help, Google ("Rating manipulation"). Checked October 8, 2026.
- Create a Google link or QR code to request reviews, Google Business Profile Help. Checked October 8, 2026.
- Trade Regulation Rule on the Use of Consumer Reviews and Testimonials (16 CFR Part 465), final rule and Statement of Basis and Purpose, Federal Trade Commission. Checked October 8, 2026.
- Federal Trade Commission Announces Final Rule Banning Fake Reviews and Testimonials, FTC press release, August 14, 2024. Checked October 8, 2026.
- Soliciting and Paying for Online Reviews: A Guide for Marketers, Federal Trade Commission, January 2022. Checked October 8, 2026.