Guides

Connect WhatsApp Agent API to n8n: Receive Messages and Send Replies

Send your WhatsApp agent a message and receive this reply from an n8n Cloud workflow:

Hello from n8n! Your message reached my WhatsApp agent.

This tutorial gives you an importable workflow that verifies incoming requests, stores a pending reply, and sends it through Unpoll. You can then replace the greeting with your own application logic.

The WhatsApp Agent API receives updates through long polling. Unpoll runs that polling and delivers signed HTTPS webhooks to your hosted n8n workspace:

Your WhatsApp agent → Unpoll → n8n Cloud → Unpoll → WhatsApp
                     polls     verifies     sends text

This is the Agent API for an agent created in WhatsApp's Settings → Agents. The agent currently replies only to its creator. n8n's WhatsApp Business Cloud node targets a different API; this example uses the Webhook, Crypto, Postgres, Code, and HTTP Request nodes instead.

What you need

  • WhatsApp Agent API access and your agent's provider token.
  • An Unpoll account.
  • An n8n Cloud workspace with Crypto node version 2 and Postgres node version 2.8. The example targets n8n 2.45.7; check your workspace version and update it if those node versions are unavailable.
  • A hosted PostgreSQL database reachable from n8n Cloud over TLS, with permission to create the example table and a dedicated receiver login.

There is no n8n server, environment-variable setup, custom node, or polling loop for you to run. PostgreSQL supplies the durable inbox: a successful webhook acknowledgement means the work is recorded, even if the reply path stops afterward.

You will use three different credentials:

CredentialWhere it belongs
WhatsApp provider tokenUnpoll, when connecting the agent
Unpoll webhook signing secretAn n8n Crypto credential
Unpoll agent API keyAn n8n Header Auth credential

Your database password also belongs in an n8n credential. Keep secrets out of Code nodes, ordinary fields, workflow variables, screenshots, and exported JSON. n8n's Crypto credentials provide a dedicated Hmac Secret field.

1. Download and import the workflow

Download these two files:

In n8n Cloud, create a workflow and choose Import from File from the workflow menu. Select the JSON file. It imports unpublished, with no credentials or pinned customer data.

The normal path is:

Webhook → Prepare signed bytes → Crypto HMAC → Verify event
        → Store inbox → Respond 204 → Claim reply → Send → Record outcome

The Recover one pending reply manual trigger joins the reply path. It is for interrupted work; it does not poll WhatsApp or run on a schedule.

Open Receive Unpoll webhook. The supplied settings are:

SettingValue
HTTP MethodPOST
AuthenticationNone — the workflow verifies Unpoll's HMAC signature
RespondUsing 'Respond to Webhook' Node
Raw BodyEnabled

Copy its Production URL, normally shaped like:

https://your-workspace.app.n8n.cloud/webhook/unpoll-whatsapp-agent

Use the URL n8n actually displays. The temporary /webhook-test/ URL only listens during a test and is unsuitable for ongoing delivery. The production URL becomes available when the workflow is published.

2. Prepare the hosted inbox

Run the downloaded schema once in your receiver database, using your database provider's SQL editor. This is a separate example table, not a change to Unpoll's database.

Create a dedicated login called n8n_receiver through your database provider, with a strong password. As the table owner, grant it only the table access the workflow needs:

GRANT USAGE ON SCHEMA public TO n8n_receiver;
GRANT SELECT, INSERT, UPDATE ON public.unpoll_n8n_inbox TO n8n_receiver;
ALTER ROLE n8n_receiver SET statement_timeout = '3s';
ALTER ROLE n8n_receiver SET lock_timeout = '2s';

Use that login for an n8n Postgres credential. Enter your provider's host, port, database, username, and password. Require SSL and leave Ignore SSL Issues off. Your database must accept connections from your n8n Cloud workspace; use your provider's connection instructions and network restrictions.

Select this credential in all three Postgres nodes:

  1. Store durable inbox
  2. Claim one pending reply
  3. Record send outcome

The queries already use bound parameters, and connection attempts are limited to three seconds. The database timeouts keep a slow or blocked insert from occupying the webhook's entire 10-second delivery budget.

The inbox stores event IDs, the agent ID, the reply recipient, and processing metadata. This fixed-greeting example does not store the received message text. Keep these records private and set a retention policy for your application; removing an event's record removes its duplicate protection. Retain IDs for the full period during which that event can be redelivered.

3. Connect your agent and signing secret

Stop any other integration polling this same agent. The provider permits one active poll per agent; competing pollers cause conflicts.

In the Unpoll dashboard, connect your agent using its WhatsApp provider token and the n8n Production URL. Save the webhook signing secret when Unpoll reveals it; it is shown once.

Do not send your test message yet. Finish the n8n configuration and publish the workflow first.

In n8n:

  1. Open Agent settings and replace REPLACE_WITH_UNPOLL_AGENT_UUID with the agent UUID from Unpoll. This is the Unpoll ID, not a WhatsApp participant ID.
  2. Open Calculate expected HMAC and create a Crypto credential.
  3. Paste the complete signing secret into Hmac Secret, including unpoll_whsec_. Do not decode it. Leave the other secret fields empty.

Keep the Crypto node at version 2, which reads the key from the credential. Its settings are Hmac, SHA256, Binary File on, binary property signed, output property expected_mac, and HEX encoding. No secret is embedded in the downloadable workflow.

New connections start from their activation cutoff. Resuming a paused agent sets a new cutoff, so earlier messages are skipped. Events ingested without an enabled webhook endpoint do not acquire deliveries later. See known limitations before using an old conversation to test.

4. Add the credential for text replies

In Unpoll, open the agent's API keys tab and create a key.

Open Send text reply in n8n. Keep Authentication → Generic Credential Type → Header Auth, and create a credential with:

FieldValue
NameAuthorization
ValueBearer followed by your Unpoll agent API key

This credential belongs on the outgoing HTTP Request node. The incoming Webhook node continues to use signature verification.

The workflow sends one request to:

POST https://api.unpoll.cc/v1/agents/{unpoll-agent-id}/messages

Its JSON body is generated from the durably claimed reply:

{
  "to": "user:<opaque-sender-id-from-the-verified-event>",
  "text": "Hello from n8n! Your message reached my WhatsApp agent."
}

to comes from the authenticated event's data.from, unchanged. It is an opaque identifier, not a phone number.

The node has a 30-second timeout, Follow Redirects off, and Retry On Fail off. Keep those settings when adapting the example. A 200 response means the provider accepted the text; delivery and read confirmations arrive later as message.status events. See sending text.

5. Publish and send a fresh message

Before publishing, check Workflow settings. The download disables saving successful, failed, and manual execution data, and disables saving execution progress. This avoids retaining raw messages and signatures in routine execution history. Do not pin real webhook data or enable detailed logging of request bodies.

Publish the workflow. Some n8n versions label this action Activate.

From the creator's WhatsApp account, send a new text message to the agent. You should receive the greeting. In Unpoll, check that the incoming message.received event's webhook delivery received 204.

In your database editor, inspect processing metadata without reading message content:

SELECT event_id, state, response_code, received_at, finished_at
FROM public.unpoll_n8n_inbox
ORDER BY received_at DESC
LIMIT 20;

The text event should become accepted, normally with response_code = 200. Delivery/read status events become ignored: they are acknowledged but never answered. Received media is also ignored by this text-only example.

Manually redelivering the same event from Unpoll should still receive 204 and should not create another reply for that event. A replay can also wake the worker to process another already-pending reply.

Because execution payloads are not saved, use the inbox metadata and Unpoll's attempt history for routine diagnosis. Test individual nodes with entirely synthetic data when you need to inspect inputs and outputs.

How signature verification and duplicates work

Unpoll signs:

timestamp + "." + exact raw HTTP body bytes

Prepare signed bytes checks the timestamp and signature formats, rejects joined duplicate authentication headers, and allows a timestamp window of 300 seconds in either direction. It obtains untouched body bytes from n8n's Raw Body output and builds the signing input as binary. It ignores n8n's parsed body field.

The Crypto node computes HMAC-SHA256 using its credential. Verify and select event compares the two 32-byte MACs with crypto.timingSafeEqual, then parses and validates the authenticated JSON, schema version, event ID, and configured agent ID. n8n Cloud makes the built-in crypto module available to Code nodes. Code node documentation

The workflow rejects unexpected content encodings and bodies over 256 KiB; n8n Cloud also applies its own request limits. A malformed or unverifiable request reaches no database or send node. Missing raw bytes or unavailable storage/credentials produce 503 so Unpoll can retry.

For a verified event, a single PostgreSQL statement inserts the inbox record under its unique event ID. A duplicate leaves the existing state intact. Only after that statement succeeds does the workflow send 204.

Reply processing runs after acknowledgement. An atomic claim moves one pending record to sending; row locks and a unique database index prevent overlapping sends for the same agent. If concurrent claims race, one may fail safely and leave work pending. HTTP calls run after the database statement commits.

This is at-least-once webhook delivery, with durable duplicate handling in your receiver. It does not promise exactly-once replies across network failures.

Recover interrupted replies safely

The starter processes one pending reply per webhook execution. The manual recovery trigger processes one additional pending reply each time you run it. There is no scheduled job consuming n8n executions while your agent is idle.

For manual recovery, choose Recover one pending reply in n8n's starting-trigger selector, then Execute workflow. Use that manual trigger rather than the Webhook test listener.

Use the stored state to decide what to do:

StateMeaning and next action
pendingWork is stored but has not been claimed. Run Recover one pending reply; repeat until no pending records remain.
sendingA request is in progress, or execution stopped after claiming it. Wait for the workflow's 60-second timeout, then reconcile it. The worker will not reclaim it automatically.
acceptedUnpoll returned a valid acceptance response. Do not resend it.
reviewA documented error establishes that the send was not accepted. Fix the cause, respect Retry-After where applicable, then deliberately requeue that record if needed.
unknownThe message may already have been sent. Check WhatsApp and status events; do not automatically retry.
ignoredStatus or non-text event; no reply was scheduled.

A timeout, lost connection, unexpected response, or 502 send_outcome_unknown becomes unknown. A crash before recording the result can leave sending. Both states hold the agent's send slot until you resolve them, preventing further automatic sends from overtaking an unresolved one.

For a confirmed-unsent record that you have reviewed, requeue that specific event in your database editor, using its real event UUID:

UPDATE public.unpoll_n8n_inbox
SET state = 'pending', send_started_at = NULL, finished_at = NULL,
    response_code = NULL, accepted_message_id = NULL
WHERE event_id = '<reviewed-event-uuid>'::uuid
  AND state IN ('review', 'sending', 'unknown');

Run this only after any previous execution has stopped and you have established that the message was not sent. If you cannot establish the outcome, keep the hold and investigate; redelivering the incoming webhook is not a safe way to force another outbound send. If the message was accepted, record the reconciled outcome as accepted instead of requeueing it.

Troubleshooting

SymptomCheck
Webhook returns 404Use the production URL and publish the workflow.
Webhook returns 401Full signing secret, Crypto v2 credential, Raw Body enabled, unmodified request bytes, and timestamp freshness.
Webhook returns 400Configured agent UUID, schema version, authenticated JSON shape, and unexpected content encoding.
Webhook returns 503Replace the agent-ID placeholder, select the Crypto/Postgres credentials, check TLS/database reachability, and confirm the inbox schema exists.
Delivery is 204 but there is no replyInspect pending, sending, review, or unknown records; run manual recovery where safe. Status/media events intentionally produce no reply.
Send returns 401 or 403Use this agent's Unpoll API key and message from the creator's account. The provider token is a different credential.
Send returns 429Respect Retry-After; keep Retry On Fail off and review the record.

Automatic webhook retries are bounded and often finish within a few minutes. After fixing a receiver outage, use Unpoll's manual redelivery while the failed delivery remains retained. An accepted event's interrupted reply is recovered from your inbox instead.

For higher throughput or automatic recovery, extend the pending-work processor and define an explicit outbound failure policy. Before adding AI or other slow work, durably store every input that work needs before 204; the sample stores only the fields needed for its fixed greeting. Keep signature checks, uniqueness constraints, and uncertain-send handling intact.

Connect your agent, import the workflow, and send your first new message. The webhook contract, signature reference, and send-error table cover the integration details as you build further.