Live AI reply agent
Answer every inbound text in seconds: the webhook-to-reply loop, real timings, the typing bubble, capacity for ad traffic, reactions, read state and reply quotes.
This is the setup for a funnel where leads text your line first, for example a Meta ad that opens iMessage with a prefilled message, and an AI agent answers each one live.
The loop
- A lead texts your line. Blue Reacher posts
message.receivedto your webhook, signed with your secret. - Your endpoint verifies the signature, answers
200straight away, and hands the message to your model. - While the model works, call
POST /v1/typingso the lead sees the "…" bubble. - Send the answer with
POST /v1/messagesandsend_mode: "instant". - Follow
message.sent,message.deliveredandmessage.readon the same webhook, keyed bymessage_id.
curl -X POST https://api.bluereacher.com/v1/messages \
-H "Authorization: Bearer brk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: reply-17c3c7ae" \
-d '{ "to": "+16465550119", "message": "Hey, thanks for reaching out. What are you looking for?", "send_mode": "instant" }'Use the inbound message_id in the Idempotency-Key. A retry after a timeout then replays the first answer instead of texting the lead twice.
How long a reply takes
Measured on live lines, from the lead's text landing to your reply leaving the line:
| Step | Typical time |
|---|---|
| Lead's text to your webhook | under 1 second |
| Your model | yours to control |
POST /v1/messages (instant) answering | 2 to 3 seconds |
| The line sending it in Messages | a few seconds, up to about 12 |
Plan for 10 to 20 seconds end to end. The last step is the line typing and sending the way a person's phone does, one message at a time, which is part of what keeps a line healthy. It is not a queue you can skip.
Three things keep your share of it small:
- Answer the webhook with
200before you call your model. Each delivery waits 10 seconds for your answer, and a slower endpoint is treated as failed and gets the event again. - Do not wait on
POST /v1/reador a reaction before you send the reply. Fire them after, or in parallel. - Turn the typing bubble on the moment the webhook arrives. A lead who sees "…" waits; a lead who sees nothing for 15 seconds reads it as a bot that broke.
curl -X POST https://api.bluereacher.com/v1/typing \
-H "Authorization: Bearer brk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "phone": "+16465550119", "device_id": "8c9a4f2e-1d3b-4a6c-9e8f-2b7c5d1a9e3f", "is_typing": true, "duration_ms": 20000 }'Use the line's device_id from GET /v1/devices; a line-limited key fills it in for you. The bubble clears by itself when your message lands. Details are on rich messaging.
Every send also gets a short typing burst of its own, from a fraction of a second up to about three seconds scaled to the message length, so the lead always sees "…" just before your reply lands. The line passes your typing request on every few seconds, so hold it for at least a few seconds; a request cleared within a second or two may never show.
How a line works
Each line is a real Apple ID signed in on Apple hardware, and every message goes out through Apple's own Messages app on that device. Apple has no server API for iMessage, so a send always passes through the device. That is why a reply takes a few seconds rather than milliseconds, and it is also why the lead sees a genuine blue bubble from a number that behaves like a person's phone, which keeps the line clear of spam filters.
Capacity for ad traffic
The daily cap on a line counts only brand-new cold contacts that you message first. A lead who texts you first never counts against it, and neither does anyone who has replied to you. That means an inbound-only funnel is limited by the request rate limits below, not by the daily cap.
| Limit | Value |
|---|---|
| Instant sends | 10 per minute per key |
POST /v1/messages | 30 per minute per workspace |
| Reactions, typing, read | 30 per minute each |
All /v1 routes | 60 per minute per workspace |
A new line still ramps its cold-contact cap over its first weeks; GET /v1/devices shows today's daily_cap. If you plan to message cold contacts from the same line as well, read rate limits first.
Attachments: one file per message. Images (JPEG, PNG, GIF, HEIC, WebP) and video (MP4, MOV) go up to 100 MB, audio and PDF up to 25 MB, all delivered at original quality over iMessage. For files that should look the same everywhere, keep images under 500 KB and videos under 10 MB. The full table is on message formatting.
Reactions, read state and reply quotes
Tapbacks: POST /v1/reactions with an emoji and the message_id you are reacting to. The answer is queued; the line applies it and the reaction's delivered field turns true in GET /v1/conversations. Reactions from the lead arrive as reaction.received.
Read state means two different things, depending on which side of the conversation you look at:
- A message the lead sent you shows
status: "read"once it has been read in your inbox: opened in the app, or cleared withPOST /v1/read. It says nothing about the lead's phone. POST /v1/readwithnative: trueand the line'sdevice_idalso asks the line to mark the thread read in Messages, so the lead's phone shows "Read" under your last reply. The answer isnative_read_queued: true. Apple sends no confirmation back, so there is no event for it; check it on a phone you own.- A message you sent shows
read, and firesmessage.read, when the lead's phone reports it read. That needs the lead to have Send Read Receipts on; with it off you will only ever seedelivered.
Reply quotes: add reply_to_message_id to POST /v1/messages and the text goes out with a quote of the message you are answering on its first line, the same as replying from the app. Pass reply_to_preview instead when you already have the text. This is a quoted reply in an ordinary bubble, not Apple's inline "Replies" thread.
When a tapback, typing bubble or read receipt does not show
Texts and these three signals take different paths on the line. A text can deliver while a tapback stays delivered: false, or a typing request answers success with no bubble on the lead's phone. If that happens, send us the message_id or the time of the request in your Slack channel. We check the line's device and fix it on our side; there is nothing to change in your code.
Before you turn the ads on
- Send yourself a text from a phone you own and time the full loop against the table above.
- Check that a webhook with a wrong signature is rejected by your endpoint.
- Confirm your retry path reuses the
Idempotency-Key. - Keep the typing bubble on, and keep your first reply short and human.
Calling and AI agents
What FaceTime calling from the API does, what it never does, and how to wire a reply-YES call flow with a person on the line or an AI voice agent on your own telephony.
GoHighLevel
The native GoHighLevel integration: two-way conversation sync, workflow actions, and where the API fits alongside it.

