response_url, and upload files.
Set up the trigger
1
Add a Webhook node
Set HTTP Method to
POST. Copy the Production URL into your bot’s webhook settings.2
Set Response Mode
Change Respond to Using ‘Respond to Webhook’ Node.
3
Add a Switch node
Route on
{{ $json.body.event }}. You will want at least webhook.verify and message.created.body, so the visitor’s text is {{ $json.body.message.text }}.
Pass verification
On thewebhook.verify branch, add a Respond to Webhook node:
- Respond With:
JSON - Response Body:
The Test URL only listens while you watch the canvas. Verify against the Production URL, with the workflow turned on.
Reply in the same request
For anything under 10 seconds, end with a Respond to Webhook node:- Respond With:
JSON - Response Body:
action_id values from them (Track package becomes track_package).
Reply later
An AI Agent or a slow API will blow past the 10-second limit. Confirm first, answer later.1
Confirm right away
Put a Respond to Webhook node straight after the trigger, and set Respond With to No Data. The typing dots stay on.
2
Do the slow work
Your AI Agent, HTTP calls, database lookups — up to 45 minutes.
3
POST the answer
Add an HTTP Request node:No auth header — the key is already in the URL.
- Method:
POST - URL:
{{ $('Webhook').item.json.body.response_url }} - Body Content Type:
JSON - Specify Body:
Using JSON - JSON:
Why the mid
Amid turns the POST into an update. If n8n retries the node, or the workflow runs twice, you get one message instead of two. Build it from event_id, and it stays the same across retries of one event.
Show progress
POST to the sameresponse_url again and again, with the same mid:
Send a file
Two HTTP Request nodes.1
Upload
- Method:
POST - URL:
{{ $('Webhook').item.json.body.response_url }}/files - Body Content Type:
Form-Data (multipart/form-data) - Add a parameter of type n8n Binary File, named
file
file.file_id.2
Post the message
- URL:
{{ $('Webhook').item.json.body.response_url }} - JSON:
Handle button clicks
Clicks arrive at the same webhook withevent set to interaction.button_clicked. Add a Switch branch and read:
{{ $json.body.action_id }}— which action{{ $json.body.value }}— your data{{ $json.body.message_metadata }}— whatever you attached to the message
response_url.
Fixing problems
The bot says nothing at all
The bot says nothing at all
The Webhook node’s Respond setting is still on its default, so n8n replied
Workflow was started before your nodes ran. Switch it to Using ‘Respond to Webhook’ Node.Verification fails with bad_challenge
Verification fails with bad_challenge
Check the workflow is active and you are using the Production URL. The wizard shows what we received — usually n8n’s default body, which means your Respond node never ran.
Duplicate messages
Duplicate messages
Retries. Set a
mid built from event_id, and re-posts become updates.Everything times out
Everything times out
You are doing the work before replying. Confirm with No Data first, then POST to the
response_url.The reply is a literal [object Object]
The reply is a literal [object Object]
You mapped an object into a text field.
{{ $json.output }} must be text. Use {{ JSON.stringify($json.output) }} if you really want the JSON.
