Skip to main content
POST
/
whatsapp
/
incoming
Receive WhatsApp Message
curl --request POST \
  --url https://your.endpoint.com/whatsapp/incoming \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: <api-key>' \
  --header 'X-API-Secret: <api-key>' \
  --data '{
  "thread_id": "<string>",
  "message_id": "<string>",
  "thread_type": "group",
  "content": "<string>",
  "sender_number": "<string>",
  "sender_name": "<string>",
  "a1_account_id": "<string>",
  "timestamp": "<string>",
  "service": "whatsapp",
  "message_type": "text",
  "is_from_agent": true,
  "message_content": {
    "text": "<string>"
  }
}'
The webhook endpoint allows you to receive incoming WhatsApp messages in real-time. When a message is received, it will be sent to your configured webhook URL with the following payload structure. Think of it as setting up an automatic forwarding system - whenever someone messages your AI agent on WhatsApp, we’ll immediately forward that message to your specified webhook URL, so your agent can process and respond to it right away.
{
  "thread_id": "chat_123456789",
  "message_id": "msg_987654321",
  "thread_type": "individual",
  "sender_number": "+61400123456",
  "sender_name": "Jane",
  "a1_account_id": "123-123-123",
  "a1_phone_number": "+1415123456",
  "timestamp": "2024-12-20T00:48:15+00:00",
  "service": "whatsapp",
  "message_type": "text",
  "is_from_agent": false,
  "message_content": {
    "text": "Hello world"
  }
}
This webhook will receive all incoming messages, including those created using the A1Base API. To prevent infinite loops, you may want to check the sender_number to avoid agents replying to themselves.

Webhook Payload

thread_id
string
Unique identifier for the chat thread
message_id
string
Unique identifier for the specific message
thread_type
string
Type of chat - can be “group”, “individual”, or “broadcast”
sender_number
string
Phone number of the message sender with country code. e.g. “+61400123456”
sender_name
string
Name of the message sender
a1_account_number
string
deprecated
Your A1Base account identifier
a1_account_id
string
Your A1Base account identifier
a1_phone_number
string
The A1Base phone number that received the message. e.g. “+61400999888”
timestamp
string
When the message was handled on WhatsApp, in ISO 8601 format. e.g. “2024-12-20T00:48:15+00:00”
service
string
The messaging service used (e.g. “whatsapp”)
message_type
string
Type of the message. Can be one of: “text”, “rich_text”, “image”, “video”, “audio”, “reaction”, “group_invite”, “location”, “unsupported_message_type”
is_from_agent
boolean
Whether the message was sent by an agent
message_content
object
The complete message content object containing all message data. Structure varies by message_type:For message_type: “text” (simple text)
{
  "text": "Hello world"
}
For message_type: “rich_text” (rich text with optional quote)
{
  "text": "This is a reply to something",
  "quoted_message_content": "Original message that was replied to",
  "quoted_message_sender": "61400123456"
}
For message_type: “image”
{
  "data": "base64_encoded_image_data"
}
For message_type: “video”
{
  "data": "base64_encoded_video_data"
}
For message_type: “audio”
{
  "data": "base64_encoded_audio_data"
}
For message_type: “reaction”
{
  "reaction": "👍"
}
For message_type: “group_invite”
{
  "groupName": "My WhatsApp Group",
  "inviteCode": "Ab12Cd34"
}
For message_type: “location”
{
  "latitude": -33.8688,
  "longitude": 151.2093,
  "name": "Optional location name",
  "address": "Optional address"
}
For message_type: “unsupported_message_type”
{
  "error": "Unsupported Message Type"
}
x-signature
string
HMAC-SHA256 signature used to verify the authenticity of the webhook. Created using your API secret and the timestamp + request body.
x-timestamp
string
Unix timestamp (in seconds) when the webhook was sent. Used to verify the request and prevent replay attacks.

Response Codes

  • 200: Message received successfully
  • 403: Invalid secret key
  • 500: Internal server error
  1. Create an endpoint in your application to receive webhook events:
import express from 'express';
import { WhatsAppIncomingData } from 'a1base-node';

const app = express();
app.use(express.json());

app.post('/whatsapp/incoming', async (req, res) => {
  const {
    thread_id,
    message_id,
    thread_type,
    sender_number,
    sender_name,
    a1_account_id,
    a1_phone_number,
    timestamp,
    service,
    message_type,
    is_from_agent,
    message_content,
  } = req.body as WhatsAppIncomingData;
  // Process the incoming message
  console.log(`Received message from ${sender_name}: ${message_content.text}`);
  
  // Always respond with 200 to acknowledge receipt
  res.json({ success: true });
});
  1. Deploy your endpoint to a public URL (e.g. using ngrok for testing)
  2. Update your webhook URL on the A1Base dashboard at https://www.a1base.com/dashboard/phone-numbers
For a complete implementation example including message handling and AI responses, see our chat agent example:
startLine: 130
endLine: 209
  • Validate the webhook payload structure matches the expected format
  • Check the sender_number to avoid infinite loops with your own agent
  • Use HTTPS endpoints only
  • Keep your webhook URL private
  • Implement rate limiting if needed
  • Add error handling for failed message processing
  • All webhook requests from A1Base include an x-signature and x-timestamp header.
  • You can verify the authenticity of each request using your API secret and the HMAC-SHA256 algorithm.
  • Here’s how the signature is generated on our side:
    const message = timestamp + JSON.stringify(body);
    const signature = crypto
      .createHmac('sha256', apiSecret)
      .update(message)
      .digest('hex');
    
On your server, do the following to verify the signature:
  1. Read the raw JSON body of the request
  2. Get the x-timestamp header
  3. Recreate the message string as timestamp + rawBody
  4. Generate your own HMAC signature with your API secret
  5. Compare it with the x-signature using a constant-time comparison
Example in Express (Node.js)
import crypto from 'crypto';
import express from 'express';
const app = express();

app.use(express.json({
  verify: (req, res, buf) => {
    req.rawBody = buf; // capture raw body for HMAC check
  }
}));

app.post('/whatsapp/incoming', (req, res) => {
  const rawBody = req.rawBody.toString();
  const timestamp = req.headers['x-timestamp'];
  const receivedSig = req.headers['x-signature'];
  const secret = process.env.A1BASE_API_SECRET;

  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(timestamp + rawBody)
    .digest('hex');

  if (receivedSig !== expectedSig) {
    return res.status(403).send('Invalid signature');
  }

  // Continue processing the verified request
  res.sendStatus(200);
});
Reject any webhook requests with a timestamp older than 5 minutes to prevent replay attacks.

Authorizations

X-API-Key
string
header
required
X-API-Secret
string
header
required

Body

application/json
thread_id
string
required

Unique identifier for the chat thread

message_id
string
required

Unique identifier for the specific message

thread_type
enum<string>
required

Type of chat

Available options:
group,
individual,
broadcast
content
string
required

The message content/text

sender_number
string
required

Phone number of the message sender

sender_name
string
required

Name of the message sender

a1_account_id
string
required

Your A1Base account identifier

timestamp
string
required

A timestamp in ISO 8601 format

service
enum<string>
required

The messaging service

Available options:
whatsapp,
telegram
message_type
enum<string>
required

Type of message content

Available options:
text,
image,
video,
audio,
document
is_from_agent
boolean
required

Whether the message is from an agent

message_content
object
required

Structured message content

Response

Message received successfully