> ## 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 WhatsApp

> Receive inbound WhatsApp messages from your users and reply within a 24-hour conversation window using native WhatsApp quoted replies.

Prelude supports 2-way messaging over WhatsApp, enabling your application to receive inbound messages from users and send contextual replies. Replies are delivered as native WhatsApp quoted messages, preserving conversation threading.

<Info>
  2-way messaging requires a connected WhatsApp Business Account (WABA). See the [WhatsApp BSP guide](/verify/v2/documentation/whatsapp) for onboarding instructions.
</Info>

## How it works

<Steps>
  <Step title="User sends a WhatsApp message">
    A user sends a text message to your WhatsApp Business number.
  </Step>

  <Step title="Prelude receives and forwards the message">
    Prelude ingests the inbound message and sends a webhook event (`inbound.message.received`) to your configured callback URL. The event includes the message content, sender phone number, and a **message ID** you can use to reply.
  </Step>

  <Step title="You send a reply">
    Your application calls the [Reply to an inbound message](/notify/v2/api-reference/send-a-reply) endpoint with the `to`, `text`, and `reply_to` fields to send a reply within the **24-hour conversation window**.
  </Step>

  <Step title="User receives a reply">
    The user sees your reply as a native WhatsApp message.
  </Step>
</Steps>

## Conversation window

WhatsApp enforces a **24-hour conversation window**. You can only reply to an inbound message within 24 hours of when the user last messaged you. After the window expires, the API returns a `window_expired` error.

Each new inbound message from the same user resets the 24-hour window.

<Warning>
  Replies outside the conversation window will be rejected. If you need to message the user after the window has closed, use a template-based message instead via the [Send a message](/notify/v2/api-reference/send-a-message) endpoint.
</Warning>

## Sending a reply

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

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

<ParamField path="to" type="string" required>
  The recipient's phone number in E.164 format. Must match the phone number that sent the original inbound message.
</ParamField>

<ParamField path="text" type="string" required>
  The reply message body, sent as a free-form WhatsApp text.
</ParamField>

<ParamField path="reply_to" type="string" required>
  The inbound message ID (prefixed with `im_`) to reply to. This ID is provided in the `inbound.message.received` webhook event.
</ParamField>

<Note>
  The `to` field must match the phone number that sent the original inbound message. A `phone_number_mismatch` error is returned otherwise.
</Note>

### SDK examples

<CodeGroup>
  ```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"),
  })
  ```
</CodeGroup>

## Receiving inbound messages

To receive inbound WhatsApp messages, configure a webhook callback URL with your Customer Success Manager. When a user sends a message to your WhatsApp Business number, Prelude forwards it as an `inbound.message.received` webhook event.

### Webhook event

```json theme={null}
{
  "id": "evt_01k8aq2zggeyssvt53zgvpx63a",
  "type": "inbound.message.received",
  "payload": {
    "message_id": "im_01k8aq2zggeyssvt53zgvpx63a",
    "channel": "whatsapp",
    "from": "+33612345678",
    "to": "+14155551234",
    "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"
}
```

<ParamField path="payload.message_id" type="string">
  The unique inbound message identifier (prefixed with `im_`). Use this value in `context.reply_to` to send a reply.
</ParamField>

<ParamField path="payload.channel" type="string">
  The channel the message was received on. Currently `whatsapp`.
</ParamField>

<ParamField path="payload.from" type="string">
  The sender's phone number in E.164 format.
</ParamField>

<ParamField path="payload.to" type="string">
  Your WhatsApp Business phone number that received the message.
</ParamField>

<ParamField path="payload.content.text" type="string">
  The text content of the inbound message.
</ParamField>

<ParamField path="payload.conversation.window_expires_at" type="RFC3339 date string">
  The timestamp when the 24-hour conversation window expires. Replies must be sent before this time.
</ParamField>

## Error handling

The following errors are specific to 2-way messaging:

| Error Code              | Description                                               | Action                                              |
| ----------------------- | --------------------------------------------------------- | --------------------------------------------------- |
| `window_expired`        | The 24-hour conversation window has closed                | Use a template-based message instead                |
| `invalid_reply_context` | The inbound message ID was not found                      | Verify the `reply_to` message ID                    |
| `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 supported for inbound messages. Media messages (images, videos, documents) are not forwarded.
* Replies are sent as **free-form text**, not as WhatsApp templates.
* 2-way messaging is only available through the **WhatsApp** channel via your connected WABA.
* The conversation window is **24 hours** from the latest inbound message and cannot be extended.
