Skip to content

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:

  1. Connect a WhatsApp channel and set up an HTTPS webhook endpoint. See Webhooks 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:

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 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).


Next steps

Navigation

Type to search…

↑↓ navigate↵ selectEsc close