Use cases

Insurance claim photos by iMessage for independent agencies

ZeroSettle gives an independent insurance agency an iMessage number that policyholders text claim photos to after an accident or storm. Each message reaches your webhook as a signed event (a text about 1–2 seconds after the line receives it, a photo with its file), and your code attaches the damage photos to the claim in your agency management system. Clients text first, so a hailstorm that brings 200 claims in a day never hits the new-contact limit; plan for replies instead, because one number sends one message at a time, normally 2–5 seconds each, so 200 replies take roughly 7–17 minutes.

Key takeaways

  • What it is: a number on your ID cards that clients text a first report of loss and damage photos to; each message reaches your server as a signed webhook.
  • Who it fits: independent agencies and small regional carriers with home and auto clients who mostly use iPhone.
  • Price: Pro is $49/month for one number. Growth, for several numbers working as one, is from $599/mo.
  • Main limit: none on incoming claims, because clients text first. On a storm day the limit is sending speed: one message at a time per number.
  • What it does not do: no SMS fallback for Android clients, no inbound video, no group threads with the adjuster, and only the first photo of a multi-photo message is delivered.

Who uses iMessage for claim photos?

iMessage claim photos suit independent insurance agencies whose home and auto clients often contact their agent first after a loss, and small regional carriers that take first reports directly. 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.

After a storm or a fender-bender, clients often don't know whether to call the agency or the carrier, a phone call carries no photos and loses details, and a client waiting for an adjuster feels abandoned. A text thread holds the first notice of loss (the first report that a loss happened), the photos and the claim number in one written record. The Texas Department of Insurance's hail-damage advice asks for both kinds of record: “Take pictures and video of the damage” and “Keep a list of everyone you talk to at your insurance company.”

The same inbound-photo pattern runs text-a-photo quotes for trades and tenant maintenance requests; law firm intake by text covers a similar first contact after an accident, and the use cases hub lists the rest.

How iMessage for claim photos works, step by step

The client texts the agency's number, ZeroSettle posts each message to your webhook, your code files the photos, and a CSR answers in the same thread. ZeroSettle has no built-in agency management system or carrier integration; those calls are your code.

  1. Publish the number on ID cards, renewal emails and the website: “Text us after an accident or loss.” On the website, an sms: link opens Messages addressed to the number; see how to get customers to text you first.
  2. The client texts what happened, then photos. Each arrives as a signed message.received event. A photo has kind: "image" and an image.url; HEIC, which iPhones shoot by default, arrives as JPEG. A voice note arrives the same way as an .m4a file.
  3. Your handler files each photo. It matches the handle (a phone number or an iMessage email address) to a policy, fetches the photo with GET /api/imessage/messages/{id}/image, attaches it to the loss in your agency management system, forwards it to the carrier and puts a 👍 tapback on it, so the client knows it arrived without another text.
  4. CSRs answer from the console with next steps and, once the carrier issues it, the claim number.
  5. Coverage stays off the thread. The thread is for intake and routing; coverage questions go to the carrier's adjuster.

Ask for one photo per text: of several photos in one message, ZeroSettle delivers only the first.

Example conversation: a tree on the garage

One storm claim, as the agency sees it in the ZeroSettle console.

  1. Sat 7:04 AM
  2. Client: tree fell on our garage in the storm last night
  3. Client: [Photo of the tree across the garage roof]👍
  4. Agency: So sorry, Linda. Is anyone hurt, and is the house itself safe to be in?
  5. Client: everyone's ok, just the garage
  6. Agency: Glad you're OK. Can you send one wide photo showing the whole garage?
  7. Client: [Wide photo of the whole garage]👍
  8. Sat 9:41 AM
  9. Agency: Claim #HX-55120 is open with your carrier. An adjuster will call within 24 hours.
Illustrative example with a fictional agency and client. Blue bubbles are sent from Summit Insurance Group's number; gray bubbles are Linda's. Each 👍 is the tapback the agency's handler puts on a photo once it is filed.

Linda's two texts and two photos are four message.received events, filed to one loss. The CSR asks about safety first, and the claim number comes from the carrier; nothing in the thread says what the policy covers.

Setup: webhook to the agency management system

One receiver verifies ZeroSettle's signature, answers 2xx at once, then files each photo and acknowledges it. It follows the webhook steps in the ZeroSettle docs; the agency module is yours, with ams and carrier standing in for your agency management system and the carrier's claims intake.

python
# claims.py: run with  uvicorn claims:app --port 8100
import asyncio, hashlib, hmac, json, os, time
from collections import defaultdict
import httpx
from fastapi import BackgroundTasks, FastAPI, Request, Response
from agency import ams, carrier                       # yours: the AMS and the carrier's claims intake

ZS = httpx.AsyncClient(base_url=os.environ.get("ZEROSETTLE_BASE_URL", "https://portal.zerosettle.io"),
                       headers={"Authorization": f"Bearer {os.environ['ZEROSETTLE_TOKEN']}"}, timeout=120)
SECRET = os.environ["ZEROSETTLE_WEBHOOK_SECRET"]
app, seen = FastAPI(), set()
client_locks, number_locks = defaultdict(asyncio.Lock), defaultdict(asyncio.Lock)

def verify(header: str, raw: bytes, tolerance: int = 300) -> bool:
    try:
        p = dict(x.split("=", 1) for x in header.split(",")); t = int(p["t"])
    except Exception:
        return False
    if abs(time.time() - t) > tolerance:
        return False
    exp = hmac.new(SECRET.encode(), f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(exp, p["v1"])

async def call(m: dict, path: str, body: dict) -> None:
    async with number_locks[m.get("line_id")]:       # one call in flight per number
        r = await ZS.post(path, json=body)
        while r.status_code == 429 and r.json()["error"]["code"] == "line_busy":
            await asyncio.sleep(2)                    # a console reply held the number
            r = await ZS.post(path, json=body)
    if r.status_code != 200:                          # e.g. 403 recipient_opted_out
        ams.flag_for_call(m["handle"], r.json()["error"]["code"])

async def reply(m: dict, text: str) -> None:
    await call(m, "/api/imessage/send", {"to": m["handle"], "line_id": m.get("line_id"), "text": text})

async def handle(m: dict) -> None:
    loss = ams.open_loss_for(m["handle"])            # policy by number or email; open or new loss
    kind = m.get("kind")
    if kind in ("image", "audio") and m.get(kind):    # a photo, or a voice note (.m4a)
        f = await ZS.get(m[kind]["url"])              # /api/imessage/messages/{id}/image or /audio
        f.raise_for_status()
        ams.attach(loss, f.content, f.headers["content-type"])
        if kind == "image":
            carrier.forward_photo(loss, f.content)
        await call(m, "/api/imessage/reactions", {"message_id": m["id"], "reaction": "like"})
    elif kind in ("image", "audio"):                  # null: the download never finished
        await reply(m, "That didn't come through. Could you send it again?")
    elif (m.get("text") or "").strip():
        ams.add_note(loss, m["text"])
    else:                                              # a video or file arrives with no file
        await reply(m, "Videos don't come through here. Please send photos, one per text.")
    ams.notify_csr(loss)                              # a CSR answers from the console

async def serialized(m: dict) -> None:
    async with client_locks[m["handle"]]:             # a text and photos land together
        await handle(m)

@app.post("/webhook")
async def webhook(req: Request, tasks: BackgroundTasks):
    raw = await req.body()
    if not verify(req.headers.get("X-ZeroSettle-Signature", ""), raw):
        return Response(status_code=401)
    ev = json.loads(raw); m = ev.get("message")
    if m and ev["event"] == "message.received" and (m["id"], ev["event"]) not in seen:
        seen.add((m["id"], ev["event"]))              # retries repeat the id and event
        tasks.add_task(serialized, m)
    return {"ok": True}

The per-number lock is the storm-day piece: ZeroSettle sends one message at a time per number, and a send that waits about 12 seconds behind others returns 429 line_busy. Holding one call in flight and retrying line_busy after 2 seconds lets a burst of claims wait its turn instead of failing. The per-client lock stops a text and a photo that land together from opening two losses. Register the receiver with PUT /api/imessage/webhook; receiving iMessages with a webhook covers signing and retries.

Limits and consent for claim photos

Claim intake uses none of ZeroSettle's new-contact limit, because anyone who texted the line first is never limited. On a storm day the ceiling is how fast one number sends your replies.

ZeroSettle limits for a claim-photo line, per number (API docs, checked October 12, 2026)
MessageProGrowth
Claims and photos clients sendUnlimitedUnlimited
Your replies in a claim thread2 unanswered in a row, then 403 awaiting_replySame
A text you start to a client who never texted5 a day50 a day, 3 per 15 min
Sending speedOne at a time, normally 2–5 sSame, on each number

Source: ZeroSettle iMessage API docs, “Send a message” and “Errors, limits, and compliance”.

Storm-day math

Two hundred claims means at least 200 replies, and a useful thread needs about three: an acknowledgement, a question and the claim number.

Sending time for storm-day replies at ZeroSettle's documented 2–5 seconds per send
RepliesAt 2 s eachAt 5 s each
200 on one numberabout 7 minabout 17 min
600 on one number20 min50 min
600 spread over 3 Growth numbersabout 7 minabout 17 min

Growth's numbers split that load only if clients text different numbers, because ZeroSettle answers each client from the line their conversation is on. Print one number per office or per line of business, such as auto and home. Every plan also refuses a third unanswered update to a client with 403 awaiting_reply, so end updates with a question.

When an auto claim mentions a stop sign

A text with the word STOP in it, or a clear request like “take me off your list”, opts the sender out of every number on your account, so “the other driver ran the stop sign” opts that client out. The claim still reaches your webhook, but replies and tapbacks return 403 recipient_opted_out, which is why the handler flags the loss for a phone call. Texting START, as the whole message, turns texts back on; see how STOP works on a ZeroSettle line.

A client who texts about a claim has started a support conversation, which generally isn't agreement to marketing. ZeroSettle's customer-support guide calls marketing in a support thread “a different consent, and it is how support lines get reported as spam,” so renewal reminders and cross-sell texts need separate consent, and nothing promotional goes out before 8 am or after 9 pm in the client's time zone, as the compliance section of the docs sets out. Consent rules vary by state and message type, so check yours with counsel. This isn't legal advice.

When ZeroSettle is not a fit for claim photos

ZeroSettle is the wrong tool for claim intake when:

  • You'd give coverage decisions by text. Keep the thread to intake and routing.
  • You'd collect full ID or financial details in the thread, such as a driver's license number or bank details for a claim payment.
  • You'd take health-plan clients' medical details in the thread. ZeroSettle's terms bar Protected Health Information unless you have signed a Business Associate Agreement with ZeroSettle.
  • Most clients use Android. iMessage reaches only iPhone and other Apple products, ZeroSettle has no SMS fallback, and on Android an sms: link opens the SMS app.
  • You need video of the damage. Inbound video doesn't arrive, though TDI tells consumers to take video too.
  • You want client, adjuster and agency in one thread. Group chats are not supported.
  • The number would be the only way to report a loss. ZeroSettle's terms say “delivery of any particular message or feature is not guaranteed,” so keep the carrier's claims phone number on the card.

How much does iMessage for claim photos cost?

A claim-photo line costs the plan price: ZeroSettle charges no per-message fees, and messages are unlimited (counted).

ZeroSettle plans for a claim-photo line (zerosettle.io, checked October 12, 2026)
PlanPriceFits
Pro$49/monthOne number to start, unlimited messages with people who message you first, 5 new contacts a day, self-serve, no setup fee, 30-day money-back guarantee
GrowthFrom $599/moSeveral numbers working as one, set up after a demo

The AI credits in each plan go unused here: on a number the hosted AI agent manages, inbound texts don't reach your webhook, so nothing would file the photos. See pricing, the customer-support recipe in the docs, or get a number.

Frequently asked questions

What about videos of the damage?

A video arrives on a ZeroSettle number as a message with no file, so ask for photos, one per text. If the carrier wants video, send its upload link in the thread: the client texted first, so the link is tappable.

Can we send renewal reminders from this number?

Only to clients who gave separate consent for those texts. A reminder to a client who has never texted the number counts as a new contact (5 a day per number on Pro, 50 on Growth), and a renewal link in it shows as plain text until they reply.

Is sensitive data safe in the thread?

Keep it out. ZeroSettle's terms say content “may not be encrypted” and that ZeroSettle “cannot guarantee communication privacy,” so ask for a policy or claim number, never an ID number or bank details. See ZeroSettle's API security.

Can several CSRs answer the same number during a storm?

Yes. Invite teammates to the ZeroSettle console. Replies sent from the console reach your webhook as message.sent, so your agency management system can log both sides.

Sources

  1. ZeroSettle iMessage API docs: “Your iMessage number”, “Send a message”, “Attachments”, “Customer support”, “Webhooks, step by step” and “Errors, limits, and compliance”. Checked October 12, 2026.
  2. ZeroSettle iMessage API Terms of Service, sections 2 and 13. Last modified September 28, 2026.
  3. ZeroSettle homepage: Pro and Growth plans. Checked October 12, 2026.
  4. Hail damage to your home or car? What to do next., Texas Department of Insurance. Checked October 8, 2026.
  5. What is the difference between iMessage, RCS, and SMS/MMS?, Apple Support. Checked October 8, 2026.
  6. SMS Links, Apple URL Scheme Reference, Apple Developer. Checked October 8, 2026.

Written by the ZeroSettle team. 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.

Product facts on this page come from the ZeroSettle iMessage API docs, the zerosettle.io homepage and the iMessage API Terms as of October 12, 2026. Outside facts come from the pages listed under Sources, checked on the dates shown there. Summit Insurance Group and Linda are fictional, and this page is not legal advice.

Text your customers from your own iMessage number

ZeroSettle gives your business its own phone number on real iMessage, running on Apple hardware we operate for you in a server facility. Your code sends with one API call and hears every reply on a signed webhook.

Pro is $49 a month with a 30-day money-back guarantee. Growth, for several numbers working as one, is from $599/mo.