How to Send RCS Messages

Send rich, branded conversations to your customers through Google's Rich Communication Services (RCS). The LigueLead API supports four template variants — plain text, single media, interactive rich cards, and horizontal carousels — created with a single request and reusable across every send.

Send RCS messages with the appropriate template type and understand the credits charged for each message.

📋 Prerequisites

  • Get a valid api-token and app-id from your LigueLead account, or an app-id created through the API.
  • Ask your account manager to enable RCS for the app-id you will use. Creating an app through the API does not enable RCS for it; your account manager must still enable RCS for that app-id. Devices that do not support RCS receive the fallback_message, when provided, through the SMS gateway.
  • Format target phone numbers as Brazilian national 11-digit numbers, international numbers with +55, or DDI numbers.

💳 RCS billing

RCS messages are charged per recipient according to the message type and, for text, its size in bytes.

Message typeRuleCredits chargedRate
Text — freeform or text templateUp to 160 bytes1 RCS Basic creditR$ 0.10
Text — freeform or text templateMore than 160 bytes2 RCS Basic creditsR$ 0.20
Media templateAny text size1 RCS Single creditR$ 0.15
Interactive card templateAny text size1 RCS Single creditR$ 0.15
Carousel templateAny text size1 RCS Single creditR$ 0.15

The 160-byte threshold applies to freeform messages and text templates. Bytes are not the same as characters: accented characters, emojis, and other Unicode characters can use more than one byte. As a result, messages with the same character count can fall into different billing tiers.

For example, a text message with 160 bytes or fewer consumes one RCS Basic credit. A text message above 160 bytes consumes two RCS Basic credits. Media, card, and carousel templates always consume one RCS Single credit.

🧭 Choose the right template type

TemplateEndpointWhen to use
TextPOST /v1/rcs/templates/textPlain branded notifications with {{N}} placeholders. No media, no buttons.
MediaPOST /v1/rcs/templates/mediaNotification with a single image or short video plus a body caption.
CardPOST /v1/rcs/templates/cardRich card with optional media and 1–4 interactive buttons (reply / open URL / dial call).
CarouselPOST /v1/rcs/templates/carousel2–10 rich cards rendered as a horizontal carousel. Buttons must be homogeneous across cards.

All four endpoints persist a template_id you can reuse on every subsequent send.

🔑 Get Your Credentials

  1. Access your account at areadocliente.liguelead.app.br.
  2. Navigate to API Credentials.
  3. Copy your api-token and app-id.

✏️ Step 1 — Create a template

Text template

POST /v1/rcs/templates/text accepts a JSON body. default_variables is an array of {key, value} pairs — each key is the numeric placeholder (e.g. "1") and value is the fallback rendered when the caller doesn't override the variable at send time.

curl -X POST "https://api.liguelead.com.br/v1/rcs/templates/text" \
  -H "api-token: YOUR_API_TOKEN" \
  -H "app-id: YOUR_APP_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "OTP Verification",
    "body": "Olá {{1}}, seu código é {{2}}",
    "default_variables": [
      { "key": "1", "value": "Cliente" },
      { "key": "2", "value": "000000" }
    ],
    "fallback_message": "Seu código LigueLead: 000000"
  }'

Response

{
  "message": "Template created successfully.",
  "data": {
    "id": "tmpl_2X8…",
    "title": "OTP Verification"
  }
}

Media template

POST /v1/rcs/templates/media accepts application/json. Provide media via either media_url (public URL) or media_file (a base64 data URI) — never both. default_variables is an array of {key, value} pairs.

curl -X POST "https://api.liguelead.com.br/v1/rcs/templates/media" \
  -H "api-token: YOUR_API_TOKEN" \
  -H "app-id: YOUR_APP_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Promo banner",
    "header": "Oferta de {{1}}",
    "body": "Aproveite enquanto durar.",
    "media_url": "https://cdn.example.com/banner.jpg",
    "default_variables": [{ "key": "1", "value": "Black Friday" }],
    "fallback_message": "Promo no nosso site: https://example.com"
  }'

To upload a local image instead of a URL, send it as a base64 data URI in the media_file field (drop media_url):

  -d '{
    "title": "Promo banner",
    "header": "Oferta",
    "body": "Aproveite enquanto durar.",
    "media_file": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."
  }'

Card template (interactive buttons)

POST /v1/rcs/templates/card adds a buttons array (1–4 entries) on top of the media contract. Each button is an object with a type discriminator:

  • type: "reply" + title + optional postback_data — quick-reply button.
  • type: "open_url" + title + url — opens a URL.
  • type: "dial_call" + title + phone_number (E.164) — dials a number.
curl -X POST "https://api.liguelead.com.br/v1/rcs/templates/card" \
  -H "api-token: YOUR_API_TOKEN" \
  -H "app-id: YOUR_APP_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Promo Card",
    "header": "Olá {{1}}",
    "body": "Confira nossa oferta",
    "media_url": "https://cdn.example.com/promo.jpg",
    "default_variables": [{ "key": "1", "value": "Cliente" }],
    "buttons": [
      { "type": "open_url", "title": "Ver oferta", "url": "https://example.com/promo" },
      { "type": "reply", "title": "Quero saber mais", "postback_data": "KNOW_MORE" }
    ]
  }'

Carousel template (2–10 cards)

POST /v1/rcs/templates/carousel extends the card contract to multiple cards under a cards array. Each card carries its own header, body, optional media (media_url or base64 data URI media_file), and buttons.

Homogeneity rule: every card must declare the same number of buttons, in the same order of types. The labels and values vary per card, but the structural shape is fixed across the carousel.

curl -X POST "https://api.liguelead.com.br/v1/rcs/templates/carousel" \
  -H "api-token: YOUR_API_TOKEN" \
  -H "app-id: YOUR_APP_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Catálogo Promo",
    "default_variables": [{ "key": "1", "value": "Cliente" }],
    "cards": [
      {
        "header": "Camiseta {{1}}",
        "body": "Tecido premium",
        "media_url": "https://cdn.example.com/camiseta.jpg",
        "buttons": [
          { "type": "open_url", "title": "Comprar", "url": "https://loja.example.com/camiseta" },
          { "type": "reply", "title": "Mais info", "postback_data": "INFO_CAMISETA" }
        ]
      },
      {
        "header": "Calça {{1}}",
        "body": "Edição limitada",
        "media_file": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
        "buttons": [
          { "type": "open_url", "title": "Comprar", "url": "https://loja.example.com/calca" },
          { "type": "reply", "title": "Mais info", "postback_data": "INFO_CALCA" }
        ]
      }
    ]
  }'

Card 0 uses a public media_url while card 1 ships its image inline as a base64 data URI in media_file. The two media options are interchangeable per card.

📤 Step 2 — Send a message

POST /v1/rcs accepts a JSON body and returns 202 Accepted immediately — actual delivery is processed asynchronously and reported back through the configured webhook. Two mutually-exclusive modes are supported:

  • Template send: pass template_id (from Step 1). Use template_variables to override the template's default_variables at send time.
  • Freeform send: pass message (capped at 306 characters).

Exactly one of template_id or message must be provided. Devices that don't support RCS receive the SMS fallback automatically — taken from the template's fallback_message for template sends, or from the message field itself for freeform sends.

The 306-character freeform limit is a technical validation rule. Text-message billing is calculated separately by byte size: up to 160 bytes consumes one RCS Basic credit, while text above 160 bytes consumes two RCS Basic credits.

Template send

curl -X POST "https://api.liguelead.com.br/v1/rcs" \
  -H "api-token: YOUR_API_TOKEN" \
  -H "app-id: YOUR_APP_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "phones": ["11999999999", "+5511888888888"],
    "template_id": "tmpl_2X8…",
    "template_variables": [
      { "key": "1", "value": "Cliente" },
      { "key": "2", "value": "12345" }
    ]
  }'

template_variables follows the same {key, value} array shape used by default_variables on the template-creation endpoints. Keys are numeric strings that match the {{N}} placeholders inside the template body.

Freeform send

curl -X POST "https://api.liguelead.com.br/v1/rcs" \
  -H "api-token: YOUR_API_TOKEN" \
  -H "app-id: YOUR_APP_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "phones": ["11999999999"],
    "message": "Olá! Confira nossa promoção: https://example.com"
  }'

Response

{
  "message": "RCS accepted successfully",
  "data": {
    "campaign_id": "01J9X…",
    "accepted_at": "2026-05-23T18:42:11.234Z"
  }
}

Persist the campaign_id — every webhook event triggered by this send echoes the same identifier so you can correlate deliveries, reads and postbacks back to the original request.

📡 Step 3 — Receive webhooks

The platform forwards delivery and inbound events to the webhook URL configured for your app-id. There are two event families relevant to RCS:

  • Delivery statusevent is one of queued, sent, delivered, read, failed, undelivered. Same payload shape as SMS and voice.
  • Inbound postbackevent: 'rcs.inbound.postback' fired when a user taps a reply button. The payload carries the postback_data you declared on the template plus the originating campaign envelope so you can correlate the click back to the send.

For the full webhook contract and signature verification, read How to Receive Webhooks.

⚠️ Common pitfalls

  • Carousel homogeneity — different button counts or button types across cards return 422. Pick the union of buttons you want and apply it to every card (mismatching only the labels/URLs).
  • media_url × media_file per card — providing both on the same card (or both for /media / /card) returns 422. media_file is a base64 data URI (data:<mime>;base64,...), not a binary upload.
  • phone_number formatdial_call buttons require E.164 (+55…). Numbers without the leading + are rejected.
  • default_variables / template_variables shape — always an array of {key, value} pairs (on both template creation and send). Sending an object ({"1":"Cliente"}) or a JSON string is rejected.
  • Numeric keys only — variable keys must match ^\d+$ so they line up with {{N}} placeholders.
  • /rcs send mode is mutually exclusive — supply either template_id or message, never both and never neither (422).
  • Freeform message is capped at 306 chars — because the same content is reused as the SMS fallback. Template sends are not bound by this cap (their fallback comes from the template you created). This character limit is separate from RCS Basic billing, which uses the 160-byte threshold for text messages.
  • /rcs payload only accepts the documented fields — unknown fields return 422. The SMS fallback is always resolved from the template's fallback_message (for template sends) or from message (for freeform sends); never sent on the request.

🔗 Endpoint reference


Did this page help you?