> ## Documentation Index
> Fetch the complete documentation index at: https://docs.prelude.so/llms.txt
> Use this file to discover all available pages before exploring further.

# 2-Way Messaging with RCS

> Receive the RCS messages your users send to your agent and reply from the same agent.

Prelude supports 2-way messaging over RCS, so your application can receive the texts users send to your RCS agent and answer them. Replies leave from the agent that received the message, as plain RCS texts.

> [!NOTE]
>
> 2-way messaging works on an RCS agent that is yours, launched for your brand. Messages sent to a shared Prelude agent are not forwarded. The agent must be a transactional, promotional or multi-use agent: an OTP agent cannot send free text, so it takes no part in 2-way messaging. Ask your Customer Success Manager to enable 2-way messaging on your agent and to register the callback URL that receives its messages.

## How it works

**1. User sends an RCS message**

A user writes to your agent from a conversation your agent opened.

**2. Prelude receives and forwards the message**

Prelude stores the message and sends an `inbound.message.received` webhook event to the callback URL registered for your agent. The event includes the message text, the sender's phone number, your agent identifier, and a **message ID** you can use to reply.

**3. You send a reply**

Your application calls the [Reply to an inbound message](https://docs.prelude.so/notify/v2/api-reference/send-a-reply.md) endpoint with the `to`, `text`, and `reply_to` fields to send a reply.

**4. User receives a reply**

The user sees your reply as an RCS text from your agent.

## Reply timing

RCS has no reply window: you can reply to an inbound message at any time, and the reply leaves from the agent that received it. Unlike WhatsApp, a reply sent more than 24 hours after the user's latest message is not rejected.

## Reserved keywords

STOP, START and HELP are handled by Prelude and are not forwarded to your callback URL. A user who sent STOP is unsubscribed from your agent until they send START. A reply to an unsubscribed user is not sent: the message fails with the reason `unsubscribed`.

## Sending a reply

To reply to an inbound message, use the dedicated [Reply to an inbound message](https://docs.prelude.so/notify/v2/api-reference/send-a-reply.md) endpoint:

```json theme={null}
{
  "to": "+33612345678",
  "text": "Thanks for reaching out! We'll look into your request.",
  "reply_to": "im_01k8aq2zggeyssvt53zgvpx63a"
}
```

**to** `string` *required*

The recipient's phone number in E.164 format. Must match the phone number that sent the original inbound message.

**text** `string` *required*

The reply message body, sent as a plain RCS text from the agent that received the message.

**reply\_to** `string` *required*

The inbound message ID (prefixed with `im_`) to reply to. This ID is provided in the `inbound.message.received` webhook event.

> [!NOTE]
>
> The `to` field must match the phone number that sent the original inbound message. A `phone_number_mismatch` error is returned otherwise.

### SDK examples

```javascript Node.js theme={null}
const response = await client.notify.reply({
  to: "+33612345678",
  text: "Thanks for reaching out! We'll look into your request.",
  reply_to: "im_01k8aq2zggeyssvt53zgvpx63a",
});
```

```python Python theme={null}
response = client.notify.reply(
    to="+33612345678",
    text="Thanks for reaching out! We'll look into your request.",
    reply_to="im_01k8aq2zggeyssvt53zgvpx63a",
)
```

```go Go theme={null}
response, err := client.Notify.Reply(context.TODO(), prelude.NotifyReplyParams{
    To:      prelude.F("+33612345678"),
    Text:    prelude.F("Thanks for reaching out! We'll look into your request."),
    ReplyTo: prelude.F("im_01k8aq2zggeyssvt53zgvpx63a"),
})
```

## Receiving inbound messages

When a user sends a text to your agent, Prelude forwards it as an `inbound.message.received` webhook event to the callback URL registered for that agent.

### Webhook event

```json theme={null}
{
  "id": "evt_01k8aq2zggeyssvt53zgvpx63a",
  "type": "inbound.message.received",
  "payload": {
    "message_id": "im_01k8aq2zggeyssvt53zgvpx63a",
    "channel": "rcs",
    "from": "+33612345678",
    "to": "acme_agent",
    "content": {
      "text": "Hi, I have a question about my order"
    },
    "conversation": {
      "window_expires_at": "2025-10-25T08:59:10.736909995Z"
    }
  },
  "created_at": "2025-10-24T08:59:10.736925502Z"
}
```

**payload.message\_id** `string`

The unique inbound message identifier (prefixed with `im_`). Use this value in `reply_to` to send a reply.

**payload.channel** `string`

The channel the message was received on: `rcs`.

**payload.from** `string`

The sender's phone number in E.164 format.

**payload.to** `string`

The identifier of your RCS agent that received the message.

**payload.content.text** `string`

The text content of the inbound message.

**payload.conversation.window\_expires\_at** `RFC3339 date string`

24 hours after the message. On RCS it is for information only: a reply sent after this time is still delivered.

## Error handling

The following errors are specific to 2-way messaging:

| Error Code | Description | Action |
| - | - | - |
| `invalid_reply_context` | The inbound message ID was not found | Verify the `reply_to` message ID |
| `reply_not_supported` | The agent that received the message takes no part in 2-way messaging: an OTP agent, or no callback URL is registered | Ask your Customer Success Manager to enable 2-way messaging on a transactional, promotional or multi-use agent |
| `phone_number_mismatch` | The `to` number does not match the inbound message sender | Use the phone number from the inbound webhook event |
| `missing_text` | The `text` field is empty or missing | Provide a reply message body |

## Limitations

* Only **text messages** are forwarded. Other content a user sends is not.
* Replies are sent as **plain RCS texts**, not as rich cards.
* 2-way messaging is only available on **your own RCS agent**, not on shared Prelude agents, and not on OTP agents.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.