/v1:
message_id and conversation_id are both on every message.inbound event, so you can act on a message the moment it arrives.
Tapbacks
A tapback is one of the six iMessage reactions: ❤️love, 👍 like, 👎 dislike, 😂 laugh, ‼️ emphasize and ❓ question.
- Only on messages you received. A tapback on a message you sent is
422 invalid_target. Custom emoji tapbacks can’t be sent. - One per message. A different type replaces your previous tapback on that message, as on an iPhone. The same type again answers
200and sends nothing, so a retry is safe. Tapbacks a teammate added in the dashboard count as yours. - Accepted, then sent. The answer is
201withstatus: "pending". Your line sends it within seconds, and then:message.reaction.addedfires withdirection: "outbound"once it’s on the contact’s phone;- or
message.reaction.failedfires with afailed_reasonif it can’t be sent. A tapback not sent within 10 minutes fails asexpired.
- Not over SMS. Tapbacks go out over iMessage, and over RCS to Android contacts. If the message came in by SMS, or the conversation is going out by SMS, the answer is
422 sms_not_supported. When iBubbl can’t tell up front, the line refuses it later withfailed_reason: "sms_target". - Same rules as a send. In a one-to-one conversation, a blocked contact gets
422 contact_blocked, an opted-out one422 contact_opted_out. Group conversations aren’t checked for blocks or opt-outs.
id. message.reaction.removed fires when it’s gone from the phone.
Read receipts
Mark a conversation read marks every message the contact sent you as read, and sends them a read receipt.- It’s for the whole conversation. Read receipts on iMessage and RCS are per conversation: the contact sees Read under their latest message, which covers everything before it. That’s why the endpoint takes a
conversation_id, not a message. - It also clears the unread badge in the iBubbl dashboard, for your whole team. Mark a conversation read when it really has been handled, by your code or a person.
- Not in groups. In a group conversation the messages are marked read in iBubbl, but no receipt is sent (
read_receipt_skipped_reason: "group"): a receipt there would reach only one participant, in the wrong chat. - Not to blocked or opted-out contacts, and not when nothing is new: the answer says
skippedand why. - Read receipts are on by default. To turn them off for your account, email support@ibubbl.com.
message.read: that event is the contact reading your message.
Typing indicators
Start typing shows the contact the three animated dots in a one-to-one iMessage conversation. They last 10 seconds, so call again every 5 seconds to keep them going. Calls closer than 3 seconds apart are answered without doing anything new. The dots go away when you send a message. If you stop calling, or call Stop typing, iBubbl stops typing straight away, but the contact’s iPhone can keep showing the dots for up to about a minute. So start typing when you’re going to reply, not “just in case”. Where typing can’t be shown, the call answers200 with status: "ignored" and an ignored_reason: group (group conversations), not_imessage (the contact uses SMS or RCS), or no_chat (the contact hasn’t messaged this line yet). Treat ignored as normal, not as an error. A blocked or opted-out contact gets 422.
Typing while your AI thinks
The most common use: a contact texts you, your AI takes a few seconds to write the reply, and meanwhile they see you typing.- On
message.inbound, mark the conversation read and start typing right away, with the event’sconversation_id. - While your model generates, call Start typing again every 5 seconds.
- Send the reply. It replaces the dots. Stop the refresh loop.
- If you decide not to reply (or the model fails), stop refreshing and call Stop typing.
id, act only on message.inbound, and use a deterministic idempotency_key. The code below is the work that receiver hands off once those checks pass.
- Refresh every 5 seconds, not faster: the dots last 10 seconds, and calls closer than 3 seconds apart do nothing new. 12 calls a minute per conversation keeps you far inside the rate limit.
- Your reply still follows the usual rules. It can wait for the contact’s sending hours, or be refused (
contact_opted_out,unresponsive_contact, …). The code stops typing whenever the send fails. - A slow model is fine. The contact sees typing for as long as you refresh. If generating takes minutes, consider a short holding reply instead.
What the contact sees
On iMessage, your read receipt shows even if the contact turned off read receipts for their own messages: that setting only controls what they send.
Webhooks
Marking a conversation read and typing fire no events. Every event above carries
conversation_id. Your own tapbacks are events about your own actions: never send a message because of one (see An auto-reply that cannot loop).
Rate limits
The five endpoints share one budget: 300 requests per 60 seconds per account, across all your API keys. It’s separate from the 120 a minute for list endpoints, so typing never slows your history reads, and the other way round. Over the limit, the answer is429 rate_limited with a Retry-After header in seconds; wait that long, then call again. A refused call doesn’t count.
At one typing refresh every 5 seconds, that’s room for about 20 conversations typing at the same moment, plus the read receipts and tapbacks around them.

