WhatsApp Agent API Webhooks: Receive Messages Without Running a Polling Loop
You want your WhatsApp agent to receive a message, run some application logic, and send a reply. An HTTP webhook would fit your application perfectly.
But the WhatsApp Agent API receives updates through long polling, not native webhook subscriptions. Your application has to request updates, track its position, and keep polling—even when nobody is sending messages.
You can build and operate that infrastructure yourself. Or you can use Unpoll to manage polling and deliver updates to your application as signed HTTPS webhooks.
This guide explains the difference, how to connect an agent, and what your webhook endpoint needs to handle safely.
Which API is this about? This guide covers the WhatsApp Agent API, not the WhatsApp Business Cloud API. Their setup and event-delivery mechanisms are different.
Does the WhatsApp Agent API support webhooks?
The WhatsApp Agent API exposes incoming messages and message receipts through:
GET https://api.whatsapp.com/agent/v1/updates
This is a long-polling endpoint. A request stays open until updates arrive or its timeout expires.
When updates arrive, the response includes a next_offset cursor. Your next request must send that value back as offset. When the provider returns an empty 204 response, you keep the existing offset.
That sounds like a small loop. The operational work appears when you need to:
- Recover after a process crashes.
- Store updates before advancing the cursor.
- Prevent multiple workers from polling the same agent.
- Handle rate limits and provider failures.
- Deliver events while your application is temporarily unavailable.
Webhooks don't remove the need for polling at the provider. They move that responsibility out of your application.
How Unpoll turns polling into webhooks
With Unpoll, the incoming message path becomes:
WhatsApp Agent API
↓ long polling
Unpoll
↓ signed HTTPS POST
Your webhook endpoint
Unpoll manages the polling loop, durably stores eligible new events before advancing the cursor, and schedules webhook delivery separately.
That separation matters: a slow or failing webhook endpoint does not block ingestion.
Your application still has responsibilities. It needs to verify requests, accept events durably, handle duplicates, and run your agent's business logic.
Connect your agent and receive a webhook
Before starting, you need:
- Access to the WhatsApp Agent API and an agent token.
- An Unpoll account.
- A publicly reachable HTTPS endpoint.
- Somewhere to securely store your webhook signing secret.
The provider currently restricts an agent's outbound messages to its creator. Use the creator's WhatsApp account for the walkthrough; this is not a setup for messaging arbitrary phone numbers.
1. Prepare your HTTPS endpoint
Choose an endpoint in your application, for example:
https://your-server.example/webhooks/whatsapp
It must accept JSON POST requests. Unpoll requires a public HTTPS URL on port 443 and does not follow redirects.
Prepare your handler to:
- Read the bounded, untouched request body.
- Verify the timestamp and signature.
- Parse and validate the event.
- Atomically store the event and schedule any application work.
- Return a successful
2xxresponse.
Keep AI inference and other slow operations out of the acknowledgement path. Store the event or enqueue it durably first, then process it separately.
2. Connect the agent in Unpoll
Open the Unpoll dashboard, enter your WhatsApp Agent API token, and set your webhook URL.
Stop any other integration polling the same agent first. The provider allows one active poll per agent; a newer poll replaces the previous one.
When the webhook endpoint is created, Unpoll reveals its signing secret once. Save it in your server's secret configuration.
There are three different credentials to keep separate:
| Credential | Purpose |
|---|---|
| WhatsApp Agent API token | Lets Unpoll communicate with the provider |
| Webhook signing secret | Lets your application verify incoming Unpoll requests |
| Unpoll agent API key | Lets your application send replies through Unpoll |
None belongs in browser-side code.
3. Configure signature verification
Deploy your endpoint's signature verification using the signing secret. Then send a new message to your agent from its creator's WhatsApp account.
Wait until the endpoint is configured before sending the test message. Events ingested without an enabled webhook endpoint are stored but are not later delivered automatically.
Unpoll starts with messages timestamped from when you connect the agent. Resuming a paused agent sets a new cutoff: messages timestamped before that resume are skipped rather than replayed. See known limitations before relying on backlog behavior.
4. Inspect the result
Check the event and delivery attempt in the dashboard.
A successful test should show:
- A
message.receivedevent. - A request arriving at your endpoint.
- Successful signature verification.
- A
2xxacknowledgement recorded for the delivery.
What does the webhook payload look like?
Each webhook contains one incoming message or one message-status update—not the provider's entire polling response.
Here is an illustrative text-message event; the identifiers are examples:
{
"id": "0199f0c6-2c3d-7e4f-8a50-61728394a5b6",
"version": "2026-10-01",
"type": "message.received",
"agent_id": "0199f0c4-1b2c-7d3e-8f40-516273849abc",
"created_at": "2026-10-19T09:10:02.345678Z",
"data": {
"from": "user:50972923564215",
"id": "wamid.HBgONTA5NzI5MjM1NjQyMTUVEgASGBQzQTVGOUMyRTFCN0Q0MDY4QUYzMQA=",
"timestamp": "1792401001",
"type": "text",
"text": {
"body": "Hello agent"
}
}
}
The two id fields serve different purposes:
ididentifies the Unpoll event. Use it to deduplicate webhook deliveries.data.ididentifies the WhatsApp message. Use it when quoting a message or marking it as read.
The sender's data.from is an opaque participant identifier. Store it and send it back unchanged; do not convert it into a phone number.
Not every incoming message is text. Check data.type before reading data.text.body.
Verify webhook signatures before trusting the payload
Unpoll sends authentication headers including:
X-Unpoll-Timestamp: <Unix timestamp in seconds>
X-Unpoll-Signature: v1=<lowercase hex HMAC-SHA256>
The signing input is:
timestamp + "." + exact raw HTTP body bytes
To verify a request:
- Validate the timestamp and signature format, rejecting missing or duplicate authentication headers.
- Reject timestamps outside your allowed window; five minutes in either direction is the recommended starting point.
- Compute HMAC-SHA256 using the entire signing secret string, including its
unpoll_whsec_prefix, as the UTF-8 key. Do not decode the secret. - Decode the supplied signature, validate its length, and compare the MAC bytes in constant time.
- Only then parse the JSON and trust its contents.
Do not parse and reserialize JSON before verification. Even a whitespace change can alter the signed bytes. Never log the signing secret or full message body as part of debugging verification failures.
Deduplicate using the verified body's id. The separate event and delivery ID headers are not covered by the signature.
See the signature verification documentation for language examples and the complete required checks.
Handle duplicates and endpoint failures
Unpoll webhook delivery is at least once, not exactly once.
For example, your application might store an event and return 204, but the response could be lost before Unpoll receives it. A retry can deliver the same event again.
Use a durable uniqueness constraint on the event ID. Recording that ID and scheduling its work should happen atomically. A duplicate that was already accepted should receive a successful acknowledgement without scheduling the same work again.
Response handling also matters:
| Your endpoint's response | Unpoll's behavior |
|---|---|
2xx | Delivery acknowledged |
408, 429, or 5xx | Retried within the configured limits |
| Connection error or timeout before response headers | Retried within the configured limits |
Other non-2xx, including redirects | Delivery fails without automatic retry |
Return success only after accepting the event durably. If temporary storage is unavailable, a 503 is more appropriate than acknowledging work you will lose.
Retries are bounded, not a promise to keep trying throughout an outage. The default budget is eight attempts within 24 hours; without Retry-After, those attempts typically finish within a few minutes. Failed deliveries can be manually redelivered from the dashboard while they remain within retention. See the retry documentation for the current policy.
Events can also arrive out of order. Don't treat webhook arrival order as conversation order.
Send a text reply
To reply, create an API key from the agent's API keys tab.
Set AGENT_ID to the Unpoll agent ID and UNPOLL_API_KEY to that key in your server environment. Replace the illustrative to value below with data.from from your verified incoming event:
curl "https://api.unpoll.cc/v1/agents/$AGENT_ID/messages" \
-H "Authorization: Bearer $UNPOLL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "user:50972923564215",
"text": "Hello! Your message reached my agent."
}'
A successful response means the provider accepted the message. Delivery and read receipts arrive separately as message.status webhooks.
Do not automatically retry a send whose outcome is unknown: the message may already have been sent, and retrying could duplicate it. Outbound sends and incoming webhook retries are different operations with different safety rules.
Unpoll supports text replies, read receipts, and downloads of received media. It does not support outbound media sends.
When should you manage polling yourself?
Managing your own poller can make sense if you want full control over ingestion and are prepared to operate persistent workers, cursor storage, recovery, and delivery infrastructure.
Unpoll is an alternative when you want your application to start at “an authenticated event arrived”, rather than “keep a polling system running.” You still need a webhook endpoint and durable event handling, but you don't need your own long-running provider poller.
You own the interesting part: deciding what your agent does with a message.
Connect your agent or follow the Unpoll quickstart to receive your first signed webhook.