---
title: "Handle inbound text messages"
description: "Learn how to receive, verify, and extract content from customer-initiated text messages in WhatsApp."
---

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

# Handle inbound text messages

When a customer sends a text message to your connected WhatsApp number, Wazapin sends a webhook delivery to your configured endpoint. 

Follow this guide to verify the webhook, check the message type, and retrieve the full text content from the Wazapin API.

## Prerequisites

Before starting, you must:
1. Connect a WhatsApp channel and set up an HTTPS webhook endpoint. See [Webhooks overview](/receive-messages/overview).
2. Configure your webhook to subscribe to the **`message.new`** event.
3. Have your **Endpoint Signing Secret** (e.g., `whsec_abc123...`) and your **API Key** ready.

---

## Processing flow

Handling an inbound message involves three primary steps:

```mermaid
graph TD
A[Wazapin sends message.new] --> B[Verify signature & svix-id]
B --> C{msg_type == 'text'?}
C -->|Yes| D[GET /v1/messages/{id} for content]
C -->|No| E[Route to other handlers]
D --> F[Process text & reply]
```

### 1. Verify the signature
To secure your endpoint, check the signature headers (`svix-signature`, `svix-timestamp`, `svix-id`) using your signing secret. See [Webhook signature verification](/api/webhook-signature-examples) for helper libraries.

### 2. Parse the payload
Verify that the message is incoming (`direction: "inbound"`) and of type text (`msg_type: "text"`). 

Here is the JSON body payload for an inbound text event:

```json Inbound text payload
{
  "message_id": "9f1fd66d-c37a-4b50-a8c2-b4dca523f9c8",
  "conversation_id": "0f89b0f9-74b4-44f9-b9b6-48f6d4de57aa",
  "contact_id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
  "channel_id": "wzp_abc123",
  "direction": "inbound",
  "from_phone": "6281234567890",
  "msg_type": "text"
}
```

### 3. Fetch the message content
Inbound webhooks are lightweight notifications and do not contain the text message body directly. Use the `message_id` from the payload to query the **GET /v1/messages/\{messageID\}** endpoint to retrieve the text content.

---

## Code example

Here is how to handle the webhook and fetch the message body:

### cURL

```bash
# Fetch the message record using the message_id from the webhook payload
curl -X GET "https://api.wazapin.com/v1/messages/9f1fd66d-c37a-4b50-a8c2-b4dca523f9c8" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Accept: application/json"
```
### TypeScript

```typescript
import express from "express";
import { Webhook } from "svix";
import { WazapinClient } from "@wazapin/sdk";

const app = express();
const wazapin = new WazapinClient({ apiKey: process.env.WAZAPIN_API_KEY });
const wh = new Webhook(process.env.WAZAPIN_WEBHOOK_SECRET!);

app.post("/webhooks/wazapin", express.raw({ type: "application/json" }), async (req, res) => {
  try {
// 1. Verify signature using raw body
wh.verify(req.body, req.headers as Record<string, string>);
  } catch (err) {
return res.status(403).send("Invalid signature");
  }

  const payload = JSON.parse(req.body.toString("utf8"));

  // 2. Filter for inbound text messages
  if (payload.direction === "inbound" && payload.msg_type === "text") {
const messageId = payload.message_id;

// 3. Fetch full message content
const { data: message } = await wazapin.messages.get(messageId);

// Inbound text body is located in message.content.text.body
const textContent = message.content?.text?.body;
console.log(`Received text from ${payload.from_phone}: ${textContent}`);

// Process message asynchronously here...
  }

  res.status(200).send("OK");
});
```
### Python

```python
from fastapi import FastAPI, Request, HTTPException
from svix.webhooks import Webhook, WebhookVerificationError
import requests
import os

app = FastAPI()
wh = Webhook(os.environ["WAZAPIN_WEBHOOK_SECRET"])
API_KEY = os.environ["WAZAPIN_API_KEY"]

@app.post("/webhooks/wazapin")
async def handle_webhook(request: Request):
body = await request.body()
try:
    # 1. Verify signature
    wh.verify(body, dict(request.headers))
except WebhookVerificationError:
    raise HTTPException(status_code=403, detail="Invalid signature")

payload = await request.json()

# 2. Filter for inbound text messages
if payload.get("direction") == "inbound" and payload.get("msg_type") == "text":
    message_id = payload.get("message_id")

    # 3. Fetch full message content
    response = requests.get(
        f"https://api.wazapin.com/v1/messages/{message_id}",
        headers={"X-Api-Key": API_KEY, "Accept": "application/json"}
    )

    if response.status_code == 200:
        message_data = response.json().get("data", {})
        text_content = message_data.get("content", {}).get("text", {}).get("body")
        print(f"Received text from {payload.get('from_phone')}: {text_content}")

        # Process message asynchronously here...

return {"ok": True}
```

---

## Troubleshooting

### Why is the message body missing from the webhook?
Wazapin sends lightweight webhooks to reduce payload size and latency. You must make a follow-up `GET /v1/messages/{messageID}` call to read the text body.

### Duplicate message deliveries
Wazapin uses at-least-once delivery for webhooks. If your server is slow to respond (takes more than 3 seconds) or returns a non-2xx status, Wazapin will retry sending the event. Always acknowledge the request with `200 OK` immediately, and process the event body asynchronously.

### Missing `from_phone`
The `from_phone` field is always populated in international format (e.g., `6281234567890`) for inbound messages. If you receive an event without `from_phone`, check if the direction is set to `outbound` (sent message status echo).

---

## Next steps

- [Handle inbound media from webhooks](/guides/handle-inbound-media)
- [Handle interactive button & list replies](/guides/handle-interactive-replies)
- [Handle message status updates](/guides/handle-delivery-status)

Source: https://docs.wazapin.id/guides/handle-inbound-text/index.mdx
