API channel: the structured event contract

Conversa Labs

Conversa Labs

Last updated on Jul 16, 2026

Overview

The API channel is a first-class inbox. Any module of the platform reaches it the same way it reaches WhatsApp or the web widget: it creates a message on a conversation. That message is then POSTed β€” signed β€” to your inbox webhook URL. Because every message payload carries the full content_type and content_attributes, structured content (interactive buttons, payment CTAs, catalog cards, calendar events and reactions) reaches your integration natively β€” you render it.

Everything here is additive and backward-compatible: existing integrations keep working and can ignore any field they don't recognize. There are no new mandatory fields.

Prerequisites

  • An API inbox with a configured webhook URL.
  • The inbox secret to verify the webhook signature (and optionally hmac_token for inbound HMAC).
  • An access token for the REST API when you send data back.

Step by step

  1. Create an API inbox and set its webhook URL.
  2. Receive events at that URL and verify the signature with the inbox secret.
  3. Read content_type + content_attributes on each message payload to render rich content.
  4. Send inbound messages, delivery receipts and reactions back through the REST API.
  5. (Optional) Restrict which events you receive with webhook_subscriptions.

Outbound content types (what the webhook delivers)

Each message_created / message_updated payload includes content_type, content, content_attributes and attachments. Beyond plain text, the API inbox may deliver:

  • input_select β€” interactive options in content_attributes.items ([{title, value}]). When a channel can't render options, the platform also appends a numbered menu to content.
  • content_attributes.payment_interactive β€” a pay action: { body, buttons: [{ type: "cta_url", text, url } | { type: "cta_copy", text, code }] }. The full charge (amount, PIX code, boleto, links) also arrives as content_attributes.payment_charge on the summary message.
  • content_attributes.catalog_product β€” a product card: { mode: "single" | "list", products: [{ id, name, price, currency, image_url, ... }] }.
  • content_attributes.calendar_event β€” a booking confirmation or reminder: { id, title, starts_at, ends_at, location, meet_url, kind: "confirmation" | "reminder" } (ISO-8601 times).
  • content_attributes.reactions β€” an array of { emoji, sender_jid }. The operator is me; a contact is stored under its own identifier. Reactions ride the message_updated event.
  • Templates β€” the sent template metadata rides additional_attributes.template_params ({ name, language, category, processed_params }).
  • Media, location, contacts, stickers β€” delivered as real attachments (plus content_attributes.media_kind for stickers).

Inbound (what you send back)

  • Incoming message β€” POST /api/v1/accounts/{account_id}/conversations/{conversation_id}/messages with message_type: "incoming". It accepts content, content_type, content_attributes and attachments, so an interactive reply is symmetric with the outbound contract.
  • Delivery receipts β€” PUT/PATCH .../messages/{id} (API inboxes only) with status of sent / delivered / read / failed (and an optional external_error). This flips the message status, which is relayed via message_updated.
  • Reactions β€” POST .../messages/{id}/react with emoji (an empty emoji removes it). Pass by=contact to record the contact's reaction (the default records the operator's). No provider is contacted for an API inbox β€” the reaction is persisted and relayed via message_updated.

Campaigns

One-off campaigns can target an API inbox. Each audience contact receives a real conversation and an outgoing message, which rides your webhook exactly like any other outbound message.

Settings & options

  • webhook_subscriptions β€” an optional array on the API channel's additional_attributes (update the inbox via the API with channel[additional_attributes][webhook_subscriptions]). When set, only those events are delivered; when absent or empty, all events are delivered (the default). Event names must be valid platform events (e.g. message_created, message_updated, conversation_created, conversation_status_changed, conversation_typing_on).
  • hmac_mandatory β€” reject inbound requests that can't be HMAC-verified.

Use cases

  • Bridge the inbox into a custom app that renders payment CTAs, catalog cards and calendar bookings.
  • Relay a contact's reaction from your own client back onto the message.
  • Mirror booking confirmations and reminders into the customer's channel of record.

Tips, limits & best practices

  • Always verify the webhook signature before acting on a payload.
  • Deduplicate by the delivery id / event; expect retries.
  • Prefer content_attributes for structured rendering; content is the plain-text fallback.
  • Subscribe only to the events you use to reduce noise.

Troubleshooting

  • No events arriving: confirm the webhook URL is set and, if you configured webhook_subscriptions, that the event you expect is in the list.
  • 422 on subscriptions: the list contains an unknown event name β€” remove it.
  • Reaction not attributed to the contact: pass by=contact on the react call.

See also