Text-to-buy over iMessage: answer a shopper's question, then send a payment link
ZeroSettle lets a DTC brand or small shop run text-to-buy over iMessage: a shopper texts a product question from a “Text us” link or a QR code on the packaging, and gets the answer and a Stripe Payment Link back in the same thread. The answer comes from ZeroSettle's hosted AI agent (Beta), set with a purchase goal and that link, or from your own model on the webhook. Because the shopper texts first, the link is tappable right away and the shopper never counts against the new-contact limit. Card details go only into Stripe's payment page, never into the thread.
Key takeaways
- What it is: a “Text us” number on product pages and packaging. Answers and a Stripe Payment Link come back in the same iMessage thread, from your brand's own number.
- Two ways to run it: the hosted AI agent (Beta) with no code, or your own model plus Stripe's
checkout.session.completedwebhook. Only the second can text “Paid ✅” back. - Main limit: shoppers text first, so the new-contact limit never applies. The hosted agent uses one AI credit per text it sends; Pro includes 1,000 a month.
- Price: Pro is $49/month for one number. Growth, for several numbers working as one, is from $599/mo.
- What it does not do: reach Android shoppers, take payment inside the conversation (no card numbers, no Apple Pay in the thread), or, with the hosted agent, check live stock or see whether a shopper paid.
Who uses iMessage for text-to-buy?
Text-to-buy over iMessage suits DTC brands, small shops, coffee roasters and course sellers with a short, well-defined product list, where the last question before buying fits in one 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 is the shopper with one question who leaves the product page to find the answer. A live-chat window closes with the tab; a text thread stays in Messages, link included. It suits a big catalog or a mostly Android audience less well (see when ZeroSettle is not a fit). After the sale, the same number can answer “where is my order?” texts, and creators run this flow from a “text me” line. More jobs are on the use-case hub.
How iMessage for text-to-buy works, step by step
The shopper starts the thread, the hosted agent or your model sends a Stripe Payment Link, and Stripe tells your server when the shopper has paid.
- Add a “Text us” link to each product page with the product prefilled, such as
sms:+15555550142&body=Question%20about%20the%2012oz%20Decaf, and print a QR code of it on bags or menus (how to build sms: links and QR codes). - The shopper texts first, so your number is a known sender: links are tappable, and the shopper is never a new contact.
- Without code: switch the line group to Managed by AI Agent (Beta), list products, prices and allowed discounts in the
business_context, and setgoaltopurchasewith your Payment Link. - With a developer: keep the line Managed by API. ZeroSettle posts each text to your webhook as
message.receivedabout 1–2 seconds after it lands. Your model answers throughPOST /api/imessage/sendand, when it detects purchase intent, sends the link with a per-shopperclient_reference_id. - The shopper pays on Stripe's hosted page, and Stripe sends
checkout.session.completedto your server. - Your handler texts “Paid ✅” and adds a 👍 tapback with
POST /api/imessage/reactions. This needs Managed by API: a line the hosted agent manages refuses your sends.
Example conversation: text-to-buy
This is the developer route, from question to confirmation.
- Today 8:12 AM
- Customer: does the 12oz bag come in decaf?
- Business: It does! Swiss Water decaf, same roast. $18 a bag, or $15 on a subscription.
- Customer: one bag pls👍
- Business: Here you go: buy.stripe.com/… Ships tomorrow.
- Customer: done
- Business: Paid ✅ Tracking comes tomorrow morning.
checkout.session.completed, not the shopper's “done”. With the hosted agent, nothing in the thread would confirm the payment, because the agent can't see Stripe.Setup: the hosted agent with goal purchase, or your model plus Stripe webhooks
Pick one route per line group, because the two don't mix: on a group the agent manages, your API sends, MCP send_imessage and the console composer are refused with 403 managed_by_zerosettle.
| Question | Hosted AI agent (Beta) | Your model (Managed by API) |
|---|---|---|
| What it knows | Your business_context and the thread | Whatever your code looks up, such as stock |
| Payment link | One link for the purchase goal | Per shopper, with a client_reference_id |
| “Paid ✅” in the thread | No | Yes |
| Cost per text | 1 AI credit | No AI credits |
No code: the hosted agent with a purchase goal
Set the agent in the console's Messaging tab or with one call. It answers everything a shopper sent since its last text in one go, as up to three texts with the typing bubble first, or sends nothing. In the docs' words, it “never invents facts your business context doesn't give, says it is your automated assistant if sincerely asked, and points anyone who wants no more texts to STOP.” Writing “No other discounts” gives it the answer to a discount request.
BASE=https://portal.zerosettle.io
curl -s -X PUT "$BASE/api/imessage/agent" \
-H "Authorization: Bearer $ZEROSETTLE_TOKEN" -H "Content-Type: application/json" \
--data-binary @- <<'JSON'
{"managed_by": "zerosettle", "goal": "purchase", "link": "https://buy.stripe.com/your_link",
"triggers": [],
"business_context": "You answer texts for Ridge Coffee. 12oz bags, House Roast or Swiss Water Decaf (same roast): $18, or $15 a bag on a subscription. No other discounts. Orders by 2 PM Pacific ship the next business day. Never ask for card numbers. You can't see payments: if they say they paid, thank them and say the confirmation comes by email. Anything else: hello@ridgecoffee.example."}
JSON
The business_context takes up to 40,000 characters. first_message (1–3 texts) goes out only when the agent starts a conversation, so text-to-buy can ignore it. The agent can't see Stripe, so the context tells it never to confirm a payment, and its triggers are off ([]): the default follow-up goes the morning after the last message and would nudge shoppers who already paid. No teammate can answer in the console while the agent manages the line, hence the email. personality and response_time are covered in the hosted AI agent explainer.
With a developer: your model, then Stripe's webhook
Your ZeroSettle webhook receiver hands each message.received to your model, as in the AI agent docs, and the model calls send_link as a tool when the shopper asks to buy. A second endpoint takes Stripe's event.
# checkout.py: run with uvicorn checkout:app --port 8200
import os, secrets, time, requests, stripe
from fastapi import BackgroundTasks, FastAPI, Request, Response
BASE = "https://portal.zerosettle.io"
ZS = {"Authorization": f"Bearer {os.environ['ZEROSETTLE_TOKEN']}"}
LINK = "https://buy.stripe.com/your_link"
ORDERS: dict[str, dict] = {} # ref -> order; keep these in your database
app = FastAPI()
def zs(path: str, body: dict) -> None:
for _ in range(5): # one send at a time per line: retry line_busy
r = requests.post(f"{BASE}/api/imessage/{path}", headers=ZS, json=body, timeout=90)
if r.status_code != 429 or r.json()["error"]["code"] != "line_busy":
return
time.sleep(2)
def send_link(handle: str, message_id: int) -> None: # the model's tool; message_id = "one bag pls"
ref = "rc_" + secrets.token_hex(8)
ORDERS[ref] = {"handle": handle, "message_id": message_id, "session": None}
zs("send", {"to": handle, "text": f"Here you go: {LINK}?client_reference_id={ref} Ships tomorrow."})
def claim_order(ref, session_id: str): # None if unknown or confirmed already
order = ORDERS.get(ref or "")
if order is None or order["session"]:
return None
order["session"] = session_id
return order
def confirm(order: dict) -> None:
zs("send", {"to": order["handle"], "text": "Paid ✅ Tracking comes tomorrow morning."})
zs("reactions", {"message_id": order["message_id"], "reaction": "like"})
@app.post("/stripe")
async def stripe_webhook(request: Request, tasks: BackgroundTasks):
try:
event = stripe.Webhook.construct_event(await request.body(),
request.headers.get("Stripe-Signature", ""), os.environ["STRIPE_WEBHOOK_SECRET"])
except Exception:
return Response(status_code=400)
s = event["data"]["object"]
if event["type"] in ("checkout.session.completed", "checkout.session.async_payment_succeeded") \
and s["payment_status"] != "unpaid":
order = claim_order(s["client_reference_id"], s["id"])
if order:
tasks.add_task(confirm, order) # answer Stripe first, text after
return Response(status_code=200)
Stripe's client_reference_id takes up to 200 letters, digits, dashes or underscores. Stripe silently drops a value with anything else in it (a phone number's +, for one) and says to keep sensitive information out of it, hence the opaque reference. Stripe also warns that fulfillment can run more than once for the same Checkout Session, so claim_order confirms each order once. like is the 👍; the other tapbacks are love, dislike, laugh, emphasize and question. See the docs on sending and webhooks.
Limits and consent for text-to-buy
Text-to-buy is shopper-initiated, so ZeroSettle's new-contact limits never apply. As the docs put it, “Anyone who texted the line first is never limited, and neither is an ongoing conversation.” What can still bite is AI credits, if the hosted agent answers, and, on every plan, the cap on unanswered texts to one shopper.
| Limit | Pro | Growth |
|---|---|---|
| New contacts a day, per number | 5; shoppers who text first don't count | 50; same |
| Unanswered texts to one shopper | 2 until they reply; texts within 5 minutes of the first count as one | |
| Messages in and out | Unlimited | Unlimited |
| AI credits (hosted agent), per account | 1,000 a month | 5,000 a month |
| Sends from one line | One at a time, normally 2–5 s | Same |
The limit math for shopper questions
- Credits: the example costs the hosted agent 2 AI credits up to the link. 300 shoppers a month at 4 agent texts each is 1,200 credits: Pro's 1,000 plus a $50 pack of 1,000, leaving 800 that never expire. At zero the agent goes quiet; texts still reach the inbox.
- A launch burst: 40 shoppers in one minute, two replies each, is 80 sends at 2–5 seconds apiece from one line, so the last answer can wait 3 to 7 minutes for sending alone.
zs()above retries429 line_busyafter 2 seconds. - The unanswered rule: if a shopper pays without texting back, the link and “Paid ✅” count as two unanswered texts when they go out more than 5 minutes apart. A next-day tracking text would be the third, and ZeroSettle refuses it with
403 awaiting_replyon every plan, so send tracking by email there.
Consent in a text-to-buy thread
The shopper texted about a product, so keep the thread to that question and the order. A purchase is not a sign-up for marketing texts.
A text with the word STOP in it opts the shopper out on every number on your account, even “how do I stop my subscription?”. Your texts and tapbacks to them then return 403 recipient_opted_out until they text START, so put subscription pause and cancel steps on your site and answer that question by email. This isn't legal advice; have counsel review your consent flow.
When ZeroSettle is not a fit for text-to-buy
ZeroSettle is the wrong tool for text-to-buy when the chat itself would have to take the payment, or when shoppers can't receive iMessage.
- You'd capture card details in the chat. Never; Stripe's hosted page takes the card.
- A large catalog needs live stock lookups, and you have no developer. The hosted agent knows only its business context and can't look anything up. Your own model on the webhook can.
- Many shoppers are on Android. There is no SMS fallback (it is on ZeroSettle's roadmap), and the
sms:link opens the SMS app, so keep email or chat beside it. - You want Apple Pay inside the conversation. Apple lists “making payments using Apple Pay” as part of Apple Messages for Business, which ZeroSettle is not; see Apple Messages for Business vs an iMessage number.
To reach opted-in shoppers who haven't texted you, abandoned checkout follow-up is the outbound counterpart.
How much does iMessage for text-to-buy cost?
Text-to-buy costs the ZeroSettle plan price, plus an AI credit pack only if the hosted agent sends more texts than the plan includes. There is no per-message fee; Stripe's processing fees are separate.
| Plan | Price | Includes |
|---|---|---|
| Pro | $49/month | One number, 5 new contacts a day, unlimited messages with people who message you first, 1,000 AI credits a month, no setup fee, 30-day money-back guarantee |
| Growth | From $599/mo | Several numbers working as one, 5,000 AI credits a month |
| AI credit packs | 1,000 for $50; 5,000 for $200 | Paid once, never expire |
See ZeroSettle pricing, or get started on Pro.
Frequently asked questions
Can shoppers pay with Apple Pay?
Yes, on Stripe's page rather than in the thread: Stripe says Apple Pay works on Payment Links with no additional configuration.
Can I use a checkout link other than Stripe's?
Yes. The hosted agent's link can be any https:// link, and your code can send any URL. Only the “Paid ✅” step needs your checkout to notify your server, as Stripe's webhook does here.
What if a shopper texts a card number anyway?
Don't use it; reply with the link. ZeroSettle keeps messages indefinitely for now, so delete that shopper's stored thread with DELETE /api/imessage/conversations/{handle}, and clear it from your own webhook logs. Nothing already delivered is unsent.
Can I text shoppers later about new products?
Only with separate marketing consent: in the US, prior express written consent, which the FCC's rule says can't be a condition of purchase. Send nothing promotional before 8 am or after 9 pm in their time zone. See iMessage marketing and TCPA consent. This isn't legal advice; ask counsel.
Sources
- ZeroSettle iMessage API docs: “Send a message” (links, reactions), “Read messages”, “Customer support” (
sms:links), “AI agent”, “Webhooks, step by step” and “Errors, limits, and compliance”. Checked October 12, 2026. - Track a payment link (the
client_reference_idURL parameter), Stripe Docs. Checked October 8, 2026. - Fulfill orders (Payment Links use Checkout;
checkout.session.completed,checkout.session.async_payment_succeeded,payment_status, fulfilling once per session), Stripe Docs. Checked October 8, 2026. - Accept payments online without writing code (Payment Links: a Stripe-hosted page, reusable with many customers), Stripe Docs. Checked October 8, 2026.
- Apple Pay (“For Payment Links or hosted Checkout, Apple Pay works with no additional configuration”), Stripe Docs. Checked October 8, 2026.
- Apple Messages for Business & Privacy (“making payments using Apple Pay”), Apple. Checked October 8, 2026.
- 47 CFR § 64.1200, Delivery restrictions, paragraph (f)(9), the FCC's TCPA rule (Legal Information Institute, Cornell Law School). Checked October 8, 2026.