Reliability & trust

Is ZeroSettle's iMessage API secure? Tokens, webhooks and data

Written by ZeroSettle. We make one of the products compared.

ZeroSettle secures its iMessage API with bearer tokens you mint and revoke in the console, and signs every webhook with HMAC-SHA256 over a timestamp and the raw body, with a 300-second tolerance against replays. ZeroSettle does not claim end-to-end encryption: its terms say message content may not be encrypted and that ZeroSettle cannot guarantee communication privacy, and its docs say messages are kept indefinitely for now unless you delete a conversation. Protected health information isn't allowed without a Business Associate Agreement, and “SOC 2, DPA and security reviews” are listed only on the Enterprise plan.

Key takeaways

  • API calls: a bearer token in the Authorization header. Minting a new token revokes nothing; revoking revokes every token on the account.
  • Webhooks: HMAC-SHA256 in X-ZeroSettle-Signature, a 300-second tolerance, HTTPS only, and a secret shown once that rotates when you re-register.
  • Data: messages are kept indefinitely for now. Deleting a conversation removes ZeroSettle's stored rows but unsends nothing.
  • Not claimed: end-to-end encryption, SOC 2 certification or HIPAA compliance. Health information needs a signed BAA first.
  • Price: tokens and signed webhooks come with Pro at $49/month and Growth from $599/mo. Enterprise (“Custom pricing”) adds “SOC 2, DPA and security reviews”.

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. Uptime and failures are covered in how reliable ZeroSettle is and on the reliability and trust hub.

How are ZeroSettle API calls authenticated?

Every call to ZeroSettle's iMessage API sends Authorization: Bearer <token>. You mint tokens in the console or with the connector's create_api_key tool, and the docs sum up their lifecycle in one line: “minting again revokes nothing, revoking revokes all.”

A missing, invalid or revoked token gets 401. A valid token can still get 402 (the subscription is not active) or 403 (the account has no iMessage number), and a bad request body gets 422, with each problem listed in error.fields. Keep the token on your server: any app, web page or repository that holds it can send as your number.

bash
# ZEROSETTLE_TOKEN comes from the environment or a secret manager, never from source
export BASE=https://portal.zerosettle.io
curl -s -o /dev/null -w '%{http_code}\n' \
  -H "Authorization: Bearer $ZEROSETTLE_TOKEN" $BASE/api/imessage/line
# 200: the token works. 401: missing, invalid or revoked. 402: subscription not active.

Rotating a token: revoke first, then mint

Revoking is all-or-nothing, so a ZeroSettle token rotation briefly cuts off every integration, and a token minted before the revoke dies with the rest.

  1. As the account owner, revoke all keys in the console. Every token on the account stops working, including your teammates' keys and the sign-ins of AI agents connected over MCP.
  2. Get a new key from the console. To use create_api_key instead, sign the connector in again first; the key it returns is shown once.
  3. Update every system that held the old one (servers, CI, Zapier or n8n credentials) and run the check above until it prints 200.
  4. Retry any send that got 401 in between.

The setup prompt in ZeroSettle's docs ends by asking for a runbook that says where the secret lives and how to rotate the token. Keep that runbook current, so step 3 takes minutes.

Tokens you never paste: OAuth for agents, 5-minute stream tokens

ZeroSettle's MCP server at https://portal.zerosettle.io/mcp uses OAuth 2.1 with dynamic client registration and PKCE: in Claude, Cursor and grok.com you sign in with your phone number, so there is no token to paste. Each client gets its own token, refreshed automatically and revocable from the console. PKCE (RFC 7636) is the OAuth extension that protects against an intercepted authorization code being redeemed by another client.

ZeroSettle's live stream (/api/imessage/stream, which the console is built on) takes a token from GET /api/imessage/stream-token that lives 5 minutes. Mint it on your server and hand the browser only the stream_url, so your bearer never reaches a web page. That URL carries its own auth, so treat it as a short-lived secret too.

How are ZeroSettle webhooks protected?

ZeroSettle signs every webhook with HMAC-SHA256 over the delivery's timestamp and raw body, using a secret only you and ZeroSettle hold, and accepts only an https:// webhook URL (plain HTTP only for localhost). A receiver that checks the signature, the timestamp and duplicates rejects forged, altered and replayed deliveries.

X-ZeroSettle-Signature: t=1757400012,v1=3f0c…(64 hex chars)

v1 is an HMAC (RFC 2104) with SHA-256 over "<t>." + raw body. Reject a t more than 300 seconds from now, and compare with a constant-time function such as Python's hmac.compare_digest. Verify the raw bytes before parsing, because re-serialized JSON no longer matches. The secret is shown once, and re-registering the webhook rotates it.

What each ZeroSettle webhook control stops (API docs, checked October 8, 2026)
RiskZeroSettle controlWhat your receiver does
A forged POST to your URLHMAC-SHA256 with your secretRecompute, compare in constant time, answer 401 on a mismatch
A body altered on the wayThe signature covers the raw bytesVerify before any JSON parsing
A captured delivery replayedt is inside the signed stringReject t over 300 s from now; dedupe inside the window
A retry arriving twiceRetries carry the same message.id and eventDedupe on that pair; order by message.id if order matters
A redirect to another hostRedirects are not followed; the URL must be https:// (http:// only for localhost)Register the final URL, not one that redirects
A leaked secretRe-registering rotates it at onceRe-register, update the stored secret, then backfill what failed in between

Answer 2xx within 10 seconds. On a non-2xx or a timeout, ZeroSettle retries 3 more times, after 1 s, 5 s and 20 s, then records the failure. The docs have ready-made Python and Node verifiers, and the webhook guide builds a full receiver around them.

Prove your receiver rejects bad deliveries

A receiver that answers 200 to everything still passes a happy-path test. Point this script at a local or staging receiver: every line should print a non-2xx code such as 401.

python
# check_receiver.py (pip install requests). Set ZEROSETTLE_WEBHOOK_SECRET to your secret, then:
# RECEIVER_URL=http://localhost:8100/webhook python check_receiver.py
import hashlib, hmac, os, time
import requests

URL = os.environ["RECEIVER_URL"]
SECRET = os.environ["ZEROSETTLE_WEBHOOK_SECRET"]
BODY = (b'{"event":"message.received","message":{"direction":"in","guid":"selftest",'
        b'"handle":"+15555550100","id":999999,"status":"received","text":"hi","ts_ms":0}}')

def attempt(sent: bytes, signed: bytes, t: int, secret: str = SECRET) -> int:
    v1 = hmac.new(secret.encode(), f"{t}.".encode() + signed, hashlib.sha256).hexdigest()
    headers = {"Content-Type": "application/json", "X-ZeroSettle-Event": "message.received",
               "X-ZeroSettle-Signature": f"t={t},v1={v1}"}
    return requests.post(URL, data=sent, headers=headers, timeout=10).status_code

now = int(time.time())
print("wrong secret ", attempt(BODY, BODY, now, secret="not-your-secret"))
print("stale, 10 min", attempt(BODY, BODY, now - 600))
print("altered body ", attempt(BODY.replace(b'"hi"', b'"ho"'), BODY, now))
print("no signature ", requests.post(URL, data=BODY, timeout=10,
                                     headers={"Content-Type": "application/json"}).status_code)

Then run the connector's test_webhook: its correctly signed synthetic message.received (header X-ZeroSettle-Test: 1) should get a 2xx. In a no-code tool, verification needs the raw body; the Zapier, n8n and Make guide shows the step.

What data does ZeroSettle keep?

ZeroSettle stores every message on the line in both directions, including failed sends, and its docs say “messages are kept indefinitely for now.” The documented way to remove them is deleting a conversation, which clears ZeroSettle's copy only.

What ZeroSettle stores and how to remove it (API docs, checked October 8, 2026)
DataStoredHow to remove or limit it
Texts, photos, voice messages, reactionsIndefinitely for now, failed sends includedDELETE /api/imessage/conversations/{handle}
Photos you sendThe exact bytes you uploadedStrip metadata before uploading
Typing indicatorsNot storedNothing to remove
Latest webhook deliverylast_delivery: URL, status code, errorRe-registering the webhook resets it

Deleting a conversation removes every stored row for that handle, media included. “Nothing already delivered is unsent, and nothing is sent to the other party.”

Photos you send: HEIC, WebP, TIFF, BMP and anything over 2048 px on the long edge are re-encoded to JPEG, which strips EXIF and GPS. A JPEG, PNG or GIF within that size goes out exactly as sent, metadata included. Either way, ZeroSettle's stored copy is the exact bytes you uploaded, so strip location data before uploading if it matters.

What customers send: photos and voice messages arrive with the file, fetched with your bearer token, so a media URL in a webhook is not a public link. Inbound HEIC or oversized images become JPEG, a Live Photo arrives as its still, and voice becomes .m4a (AAC) with duration_ms. Video and files arrive as a row with no file; of several photos, only the first is delivered.

Legal requests: section 12 of ZeroSettle's terms says ZeroSettle “may also disclose user content if such disclosure is necessitated by law, to comply with legal processes, or to enforce these Terms.”

Is ZeroSettle's iMessage API end-to-end encrypted?

No. ZeroSettle does not claim end-to-end encryption, and its terms say content “may not be encrypted” and that “ZeroSettle cannot guarantee communication privacy.”

Message text reaches your webhook and GET /api/imessage/messages as plain JSON and shows in the console, so treat every thread as readable by ZeroSettle's service and by anyone holding your token. Social Security numbers, card numbers and identity documents belong in a secure form you link to, as on a law firm intake line, where documents go to an upload link.

  1. Today 4:12 PM
  2. Customer: The lease renewal asks for my SSN. Can I just text it to you?
  3. Business: Please don't send your SSN or bank details by text. Our secure renewal form takes them: renew.example.com/dana
  4. Customer: ok, submitted
  5. Business: Got it, thanks Dana. We'll text you here once it's countersigned.👍
Illustrative example with a fictional property manager. Blue bubbles are sent from the business's number; gray bubbles are the customer's. The link is tappable because Dana texted first.

Is ZeroSettle SOC 2 certified or HIPAA compliant?

ZeroSettle does not claim SOC 2 certification or HIPAA compliance. The homepage lists “SOC 2, DPA and security reviews” only on the Enterprise plan (“Custom pricing”, “For high volume and compliance needs”), whose Contact sales address is founders@zerosettle.io.

ZeroSettle's iMessage API Terms bar using the service “to create, receive, maintain, or transmit Protected Health Information” unless you have signed a Business Associate Agreement (BAA) with ZeroSettle. Whether HIPAA or another privacy law applies to your messages depends on who you are and what you send, so check with your counsel; this isn't legal advice. For access, invite teammates to the console rather than sharing the owner's login.

How do other iMessage APIs authenticate webhooks?

Signed webhooks are not universal. Of seven other iMessage APIs whose public docs we checked on October 8, 2026, three document an HMAC-SHA256 signature. The other four document a shared secret in a header or the URL, or no check at all.

Webhook authentication in each provider's public docs (checked October 8, 2026)
ProviderHow a webhook is authenticatedReplay window documented
ZeroSettleHMAC-SHA256 of "<t>." + raw body in X-ZeroSettle-SignatureYes, 300 s
LinqStandard Webhooks HMAC-SHA256 over id, timestamp and bodyYes, 5 minutes
BlooioHMAC-SHA256 in X-Blooio-Signature, with secret rotationYes, 300 s in sample code
Texting BlueHMAC-SHA256 of the body in x-textingblue-signatureNo
SendblueOptional shared secret in a header; no HMAC payload signature documentedNo
LoopMessageAn authorization header you set; no HMAC documentedNo
Project BlueA secret in the webhook URL; “no signature or HMAC header”No
BlueBubbles (self-hosted)None documented; the server password authenticates REST calls (as a URL query parameter), not webhooksNo

Based on each provider's public documentation; a provider may support more than it documents.

When ZeroSettle is not a fit for your security requirements

ZeroSettle is the wrong choice today if you need:

  • End-to-end encryption or a promise of privacy. The terms say ZeroSettle “cannot guarantee communication privacy.”
  • SOC 2, a DPA or a security review on Pro or Growth. Only the Enterprise plan lists “SOC 2, DPA and security reviews”.
  • To send health information without first signing a BAA with ZeroSettle.
  • Automatic retention limits. Removal is one conversation at a time.
  • Revoking one token while others keep working. Revoking revokes all.

Security checklist for your ZeroSettle integration

ZeroSettle signs every webhook and checks every token, but the checks on your side are code you write. Before going live:

  1. Keep the token and webhook secret in environment variables or a secret manager, never in source.
  2. Verify every signature on the raw bytes, reject t outside 300 seconds, and dedupe on (message.id, event).
  3. Never put secrets in an sms: prefill: the prefilled text becomes a stored message in the thread.
  4. Keep SSNs, card numbers and documents out of threads; send a secure link once the customer has replied.
  5. When someone with token access leaves, revoke, mint and redeploy; if they saw the webhook secret, re-register to rotate it.

Ready to build? Get a number, compare plans and pricing, or read the webhook steps in the ZeroSettle docs.

Frequently asked questions

Can I rotate the webhook secret without losing messages?

Yes, with a backfill. Re-registering rotates the secret at once, so until your server has the new one, deliveries fail verification and are recorded as failed after the retries at 1, 5 and 20 seconds. ZeroSettle still stores every message, so page GET /api/imessage/messages?after_id= from the last message.id you handled to catch anything missed.

Can a token be limited to one number or one endpoint?

ZeroSettle's docs describe no token scopes. On an account with several numbers, every number shares the same API key, webhook and stream, so treat a token as access to all of them.

Does deleting a conversation remove it from the customer's phone?

No. It removes ZeroSettle's stored copy only. Unless they text first, your next message to that person counts as a new contact and re-shares your name and photo.

Does my AI provider's API key ever go to ZeroSettle?

Not when you bring your own model: “The model, the prompt, the provider account, and the API keys are yours and never touch ZeroSettle.” The hosted AI agent (Beta) needs no key of yours at all; it answers from the business context you write, so leave out anything a customer shouldn't read.

Sources

  1. ZeroSettle iMessage API docs: “Your iMessage number”, “Authentication”, “Attachments”, “Read messages”, “AI agent”, “Webhooks, step by step”, “Live stream (SSE)”, “Connect your AI agent”, “MCP” and “Errors, limits, and compliance”. Checked October 8, 2026.
  2. ZeroSettle iMessage API Terms of Service, sections 12 and 13. Last modified September 28, 2026.
  3. zerosettle.io, Plans (Enterprise card). Checked October 8, 2026.
  4. RFC 2104: HMAC: Keyed-Hashing for Message Authentication, RFC Editor. Checked October 8, 2026.
  5. RFC 7636: Proof Key for Code Exchange by OAuth Public Clients, RFC Editor. Checked October 8, 2026.
  6. hmac: Keyed-Hashing for Message Authentication, Python documentation. Checked October 8, 2026.
  7. Standard Webhooks. Checked October 8, 2026.
  8. Webhooks, Linq docs. Checked October 8, 2026.
  9. Webhook signatures, Blooio docs. Checked October 8, 2026.
  10. Webhooks, Texting Blue docs. Checked October 8, 2026.
  11. Webhooks, Sendblue docs. Checked October 8, 2026.
  12. Conversation API webhooks, LoopMessage docs. Checked October 8, 2026.
  13. Webhook retries, timeouts and delivery status, Project Blue support. Checked October 8, 2026.
  14. REST API and webhooks, BlueBubbles docs. Checked October 8, 2026.

Changelog

  • First published. This page is reviewed quarterly, and after any security incident or new published data.

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 8, 2026. Other providers' facts come from their own public docs, checked the same day. The property manager in the example is fictional.

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.