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>",
"content": "<string>",
"sender_number": "<string>",
"sender_name": "<string>",
"a1_account_id": "<string>",
"timestamp": "<string>",
"is_from_agent": true,
"message_content": {
"text": "<string>"
}
}
'import requests
url = "https://your.endpoint.com/whatsapp/incoming"
payload = {
"thread_id": "<string>",
"message_id": "<string>",
"content": "<string>",
"sender_number": "<string>",
"sender_name": "<string>",
"a1_account_id": "<string>",
"timestamp": "<string>",
"is_from_agent": True,
"message_content": { "text": "<string>" }
}
headers = {
"X-API-Key": "<api-key>",
"X-API-Secret": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-API-Key': '<api-key>',
'X-API-Secret': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
thread_id: '<string>',
message_id: '<string>',
content: '<string>',
sender_number: '<string>',
sender_name: '<string>',
a1_account_id: '<string>',
timestamp: '<string>',
is_from_agent: true,
message_content: {text: '<string>'}
})
};
fetch('https://your.endpoint.com/whatsapp/incoming', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://your.endpoint.com/whatsapp/incoming",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'thread_id' => '<string>',
'message_id' => '<string>',
'content' => '<string>',
'sender_number' => '<string>',
'sender_name' => '<string>',
'a1_account_id' => '<string>',
'timestamp' => '<string>',
'is_from_agent' => true,
'message_content' => [
'text' => '<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>",
"X-API-Secret: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://your.endpoint.com/whatsapp/incoming"
payload := strings.NewReader("{\n \"thread_id\": \"<string>\",\n \"message_id\": \"<string>\",\n \"content\": \"<string>\",\n \"sender_number\": \"<string>\",\n \"sender_name\": \"<string>\",\n \"a1_account_id\": \"<string>\",\n \"timestamp\": \"<string>\",\n \"is_from_agent\": true,\n \"message_content\": {\n \"text\": \"<string>\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("X-API-Secret", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://your.endpoint.com/whatsapp/incoming")
.header("X-API-Key", "<api-key>")
.header("X-API-Secret", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"thread_id\": \"<string>\",\n \"message_id\": \"<string>\",\n \"content\": \"<string>\",\n \"sender_number\": \"<string>\",\n \"sender_name\": \"<string>\",\n \"a1_account_id\": \"<string>\",\n \"timestamp\": \"<string>\",\n \"is_from_agent\": true,\n \"message_content\": {\n \"text\": \"<string>\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://your.endpoint.com/whatsapp/incoming")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["X-API-Secret"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"thread_id\": \"<string>\",\n \"message_id\": \"<string>\",\n \"content\": \"<string>\",\n \"sender_number\": \"<string>\",\n \"sender_name\": \"<string>\",\n \"a1_account_id\": \"<string>\",\n \"timestamp\": \"<string>\",\n \"is_from_agent\": true,\n \"message_content\": {\n \"text\": \"<string>\"\n }\n}"
response = http.request(request)
puts response.read_bodyWebhook
Webhook
Webhook endpoint for receiving incoming WhatsApp messages from WhatsApp servers
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>",
"content": "<string>",
"sender_number": "<string>",
"sender_name": "<string>",
"a1_account_id": "<string>",
"timestamp": "<string>",
"is_from_agent": true,
"message_content": {
"text": "<string>"
}
}
'import requests
url = "https://your.endpoint.com/whatsapp/incoming"
payload = {
"thread_id": "<string>",
"message_id": "<string>",
"content": "<string>",
"sender_number": "<string>",
"sender_name": "<string>",
"a1_account_id": "<string>",
"timestamp": "<string>",
"is_from_agent": True,
"message_content": { "text": "<string>" }
}
headers = {
"X-API-Key": "<api-key>",
"X-API-Secret": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-API-Key': '<api-key>',
'X-API-Secret': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
thread_id: '<string>',
message_id: '<string>',
content: '<string>',
sender_number: '<string>',
sender_name: '<string>',
a1_account_id: '<string>',
timestamp: '<string>',
is_from_agent: true,
message_content: {text: '<string>'}
})
};
fetch('https://your.endpoint.com/whatsapp/incoming', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://your.endpoint.com/whatsapp/incoming",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'thread_id' => '<string>',
'message_id' => '<string>',
'content' => '<string>',
'sender_number' => '<string>',
'sender_name' => '<string>',
'a1_account_id' => '<string>',
'timestamp' => '<string>',
'is_from_agent' => true,
'message_content' => [
'text' => '<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>",
"X-API-Secret: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://your.endpoint.com/whatsapp/incoming"
payload := strings.NewReader("{\n \"thread_id\": \"<string>\",\n \"message_id\": \"<string>\",\n \"content\": \"<string>\",\n \"sender_number\": \"<string>\",\n \"sender_name\": \"<string>\",\n \"a1_account_id\": \"<string>\",\n \"timestamp\": \"<string>\",\n \"is_from_agent\": true,\n \"message_content\": {\n \"text\": \"<string>\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("X-API-Secret", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://your.endpoint.com/whatsapp/incoming")
.header("X-API-Key", "<api-key>")
.header("X-API-Secret", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"thread_id\": \"<string>\",\n \"message_id\": \"<string>\",\n \"content\": \"<string>\",\n \"sender_number\": \"<string>\",\n \"sender_name\": \"<string>\",\n \"a1_account_id\": \"<string>\",\n \"timestamp\": \"<string>\",\n \"is_from_agent\": true,\n \"message_content\": {\n \"text\": \"<string>\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://your.endpoint.com/whatsapp/incoming")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["X-API-Secret"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"thread_id\": \"<string>\",\n \"message_id\": \"<string>\",\n \"content\": \"<string>\",\n \"sender_number\": \"<string>\",\n \"sender_name\": \"<string>\",\n \"a1_account_id\": \"<string>\",\n \"timestamp\": \"<string>\",\n \"is_from_agent\": true,\n \"message_content\": {\n \"text\": \"<string>\"\n }\n}"
response = http.request(request)
puts response.read_bodyThe 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
string
Unique identifier for the chat thread
string
Unique identifier for the specific message
string
Type of chat - can be “group”, “individual”, or “broadcast”
string
Phone number of the message sender with country code. e.g. “+61400123456”
string
Name of the message sender
string
deprecated
Your A1Base account identifier
string
Your A1Base account identifier
string
The A1Base phone number that received the message. e.g. “+61400999888”
string
When the message was handled on WhatsApp, in ISO 8601 format. e.g. “2024-12-20T00:48:15+00:00”
string
The messaging service used (e.g. “whatsapp”)
string
Type of the message. Can be one of: “text”, “rich_text”, “image”, “video”, “audio”, “reaction”, “group_invite”, “location”, “unsupported_message_type”
boolean
Whether the message was sent by an agent
object
The complete message content object containing all message data. Structure varies by message_type:For message_type: “text” (simple text)For message_type: “rich_text” (rich text with optional quote)For message_type: “image”For message_type: “video”For message_type: “audio”For message_type: “reaction”For message_type: “group_invite”For message_type: “location”For message_type: “unsupported_message_type”
{
"text": "Hello world"
}
{
"text": "This is a reply to something",
"quoted_message_content": "Original message that was replied to",
"quoted_message_sender": "61400123456"
}
{
"data": "base64_encoded_image_data"
}
{
"data": "base64_encoded_video_data"
}
{
"data": "base64_encoded_audio_data"
}
{
"reaction": "👍"
}
{
"groupName": "My WhatsApp Group",
"inviteCode": "Ab12Cd34"
}
{
"latitude": -33.8688,
"longitude": 151.2093,
"name": "Optional location name",
"address": "Optional address"
}
{
"error": "Unsupported Message Type"
}
string
HMAC-SHA256 signature used to verify the authenticity of the webhook. Created using your API secret and the timestamp + request body.
string
Unix timestamp (in seconds) when the webhook was sent. Used to verify the request and prevent replay attacks.
Response Codes
200: Message received successfully403: Invalid secret key500: Internal server error
Setting up your webhook
Setting up your webhook
- 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 });
});
- Deploy your endpoint to a public URL (e.g. using ngrok for testing)
- Update your webhook URL on the A1Base dashboard at https://www.a1base.com/dashboard/phone-numbers
startLine: 130
endLine: 209
Security best practices
Security best practices
- 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-signatureandx-timestampheader. - 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');
- Read the raw JSON body of the request
- Get the x-timestamp header
- Recreate the message string as timestamp + rawBody
- Generate your own HMAC signature with your API secret
- Compare it with the x-signature using a constant-time comparison
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.
Body
application/json
Unique identifier for the chat thread
Unique identifier for the specific message
Type of chat
Available options:
group, individual, broadcast The message content/text
Phone number of the message sender
Name of the message sender
Your A1Base account identifier
A timestamp in ISO 8601 format
The messaging service
Available options:
whatsapp, telegram Type of message content
Available options:
text, image, video, audio, document Whether the message is from an agent
Structured message content
Show child attributes
Show child attributes
Response
Message received successfully

