Skip to main content
When a contact texts your line, iBubbl POSTs a message.inbound event to your webhook endpoint. This guide shows what that event contains, how to handle attachments and STOP replies, and a complete auto-reply receiver that’s safe to run in production. You need a webhook subscription that includes message.inbound. The Quickstart creates one, and Test webhooks locally shows how to point one at your laptop.

The message.inbound event

Only messages from your contacts fire message.inbound. Messages your line sends, whether through the API or the dashboard, fire message.queued, message.sent and the other status events instead, never message.inbound.

Group messages

In a group, conversation_kind is "group", sender_phone is the participant who posted, and participants lists the group. The event doesn’t carry the group’s chat_guid: to reply into the group, call GET /v1/messages/{message_id} with the inbound message_id and send with the chat_guid it returns. See Group conversations.

Attachments

When the contact sends images, GIFs, videos, audio or files, the event fires once, after every attachment has been fetched, and attachments lists them:
  • url is a signed link that expires after 24 hours (expires_at). If you need the file, download it and store it yourself as soon as the event arrives; a retried delivery carries the same URL, so a late retry may already be expired.
  • An attachment iBubbl couldn’t fetch has "unavailable": true and no url. Don’t treat it as an error; the text (if any) is still in body.
  • kind is image, gif, video, audio or file. Unsupported types are left out of the array.

STOP and opt-outs

If the whole message is a STOP keyword (stop, unsubscribe and the others on Suppression and opt-outs), iBubbl records the opt-out before it fires message.inbound, and fires a separate contact.opted_out event with source: "auto_keyword". You receive both events, in either order.
  • Mark the contact do-not-contact in your system when contact.opted_out arrives.
  • Any send to them after that returns 422 contact_opted_out (in a group, 422 group_outbound_blocked_suppression). Treat it as final.

An auto-reply that cannot loop

An auto-reply is where most messaging integrations go wrong: a reply triggers an event, which triggers a reply, and the line sends hundreds of messages. The receiver below avoids that with five rules:
  1. Verify the signature on the raw body, before parsing it. See Signature verification.
  2. De-duplicate on the event id, and acknowledge at once. Deliveries are at-least-once; return 200 before doing slow work. See Retries and deduplication.
  3. Act only on message.inbound. Never send because of message.sent, message.delivered, message.read, message.failed or any other event about your own messages: that is the classic loop.
  4. Use a deterministic idempotency_key: "auto-reply-" + message_id. However many times the same inbound event reaches you, iBubbl sends that reply at most once.
  5. Reply at most once per conversation per cooldown. If the other side is also a bot, it answers your auto-reply and you answer back. A contact’s reply resets iBubbl’s unanswered limit, so that limit won’t stop a bot-to-bot exchange; your cooldown will.
It also resolves where to reply with GET /v1/messages/{id}, so an Apple ID email in from_number or a group conversation never breaks the send, and it treats every 4xx as final.
A few notes on the design:
  • Why both de-duplication and a deterministic key? The event-id check skips work you’ve already done. The key is the guarantee: even if two instances of your app process the same event at the same moment, iBubbl sends that reply once. The Idempotency page explains replays.
  • STOP needs no special case. The opt-out is recorded before message.inbound fires, so a reply to a STOP comes back 422 contact_opted_out and the code stops.
  • Working hours still apply. An auto-reply sent outside the contact’s sending hours waits as pending until they open. The one thing allowed to answer straight away outside sending hours is the built-in after-hours auto-reply workflow; use it for “we’re closed” replies instead of building your own.
  • Missed webhooks. If your endpoint is down for longer than the ~37-minute retry window, those events are gone. Recover from downtime lists the messages you missed with GET /v1/messages and runs them through the same de-duplication.
  • Groups. The sample replies into groups too. Many integrations prefer to auto-reply only in one_to_one conversations: return early when conversation_kind is "group".

Next

Signature verification

The verifySignature used above, with a test vector.

Test webhooks locally

Run this receiver on your laptop with a tunnel.

Retries and deduplication

The retry schedule and why events arrive twice or out of order.

Webhook events

Every event type and its payload.