Handle inbound text messages
Learn how to receive, verify, and extract content from customer-initiated text messages in WhatsApp.
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:
- Connect a WhatsApp channel and set up an HTTPS webhook endpoint. See Webhooks overview.
- Configure your webhook to subscribe to the
message.newevent. - Have your Endpoint Signing Secret (e.g.,
whsec_abc123...) and your API Key ready.
Processing flow
Handling an inbound message involves three primary steps:
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 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:
{
"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:
# 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"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");
});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).