Webhook Node (Trigger)
Trigger your agent via HTTP. Multiple webhooks per agent supported — key constraints explained.
Written By Philippe
Last updated 3 days ago
Webhook Node (Trigger)
⚠️ Limitations — Works with known restrictions, see details below
Compatibility
Status
This integration works on indie.money with known limitations. See the details above for specifics.
Caller sign-in
Basic Auth, Header Auth and JWT Auth take their secret from a credential saved in your Secure Vault. You save it in the same step as any other credential the workflow needs.
- Save the secret as a credential. When you set up your copy, the credential wizard asks for each sign-in secret the workflow declares: the header name and value for Header Auth, the user and password for Basic Auth, the passphrase or public key for JWT Auth. You cannot turn the workflow on until every one is saved.
- Wrong or missing secret. The caller gets a 401. The workflow does not run, and the call costs you nothing.
- Change the secret. Rotate it from your Vault page. The new secret applies within about a minute. You do not need to turn the workflow off and on again. If you change the header name of a Header Auth credential, turn the workflow off and on again: until you do, every call gets a 401 and the workflow does not run.
- JWT Auth. A passphrase works with HS256, HS384 and HS512. A PEM public key works with RS256 to RS512 and ES256 to ES512.
See Configure Credentials for a Workflow.
What the last node answers
When the Webhook node answers after the last node, the Response Data option picks the answer, as in n8n:
The response code, response headers and Response Content-Type options apply to this answer.
Moving from the old default. The first-entry answer used to be an array holding the first item. It is now the object itself, as in n8n. A caller that read body[0] must read body. To get an array, choose All Entries.
No Respond to Webhook node. A successful run in Respond to Webhook mode that never reaches a Respond to Webhook node answers an empty 200.
Request body and response code
- Response Code — Both node versions honour the code you set. It applies when the node answers at once and to a last-node answer. A failed run answers
500only when the node answers after the last node or from a Respond to Webhook node. See When a run fails. - Response Data — An expression in it is evaluated, with the request as
$json, as in n8n. - Raw Body —
$json.bodystays parsed. The raw bytes are in the item's binary fielddata. - Form posts — A
application/x-www-form-urlencodedbody is parsed into$json.bodyas an object (up to 1,000 fields). An XML body is parsed too, and XML that does not parse answers422. Atext/plainbody arrives as text.
When a run fails
When the Webhook node answers after the last node or from a Respond to Webhook node, a failed run answers 500 with {"message":"Error in workflow"}, as in n8n. The response code you set on the node does not apply to a failed run. The answer names no node and no error, because anyone can call a webhook. Read the run in your execution history to find the step that failed.
When the node answers immediately, a failed run gets the same answer as a successful run: {"message":"Workflow was started"}, your Response Data text, or no body. The caller sees no error, as in n8n. On the platform, this answer comes when the run ends, not when it starts.
A trigger that fails before it answers gets 500 with {"code":0,"message":"Workflow Webhook Error: Workflow could not be started!"}, as in n8n. Your execution history keeps the real error.
A sender that retries on a 5xx answer, as many services do, runs the workflow again, and each run is charged. To stop the retries, answer from the error path yourself: add a Respond to Webhook node on the error output of the step that can fail, and choose the response code there.
When a trigger returns no data
Some triggers check a call and return no data, as in n8n. Examples: a Telegram update that your trigger filters out, a Stripe call with a bad signature, or a Chat Trigger that loads a previous session. Then no run starts and the run price is not charged. The caller gets the answer the trigger sends, or {"message":"Webhook call received"}.
The call itself still counts:
- On the Caller pays link, the payment-processing fee is still charged, because the payment was processed.
- On a free trial, the call still uses one trial run.
Your execution history shows the call with no steps and no run price.
Repeat answers from cache {#cache}
indie.money can answer a repeat call from a stored copy, without running your workflow again. n8n has no such cache: there, every call runs the workflow. You turn it on with the standard header n8n already offers, and it works only on the You pay link.
Set the Cache-Control response header on the Webhook node or on a Respond to Webhook node, for example public, max-age=300. A repeat GET to the You pay link with the same query, in the same order, then gets the stored answer for that many seconds. The workflow does not run and your Workflow balance is not charged. The answer carries X-Indie-Cache: HIT.
- ⚠️ Every caller gets the same answer. The stored answer is not tied to who is calling. Anyone who calls that link in the lifetime gets it, whatever their headers or sign-in. Never set
publicon an answer that depends on the caller. - The Caller pays link never uses the cache. Every paid call runs and is charged, even when the answer sets
Cache-Control. - Asking for a fresh answer. A call that sends
Cache-Control: no-cacheormax-age(a browser reload does) always runs and is charged, and its answer replaces the stored one. - Where it is stored. Each region keeps its own copy, so the first call in each region runs and is charged.
- Answers that depend on the caller. Name the request headers the answer depends on in a
Varyresponse header, for exampleVary: x-user-id. A stored answer then goes only to a caller who sends the same values of those headers. A caller who does not send a named header gets the answer stored for callers who did not send it either. WithoutVary, every caller of the same address and query gets the same stored answer. - Webhooks with caller sign-in. They are cached only when the answer names a
Varyheader. A stored answer then goes only to the same login with the same header values. The sign-in is still checked on every call, so a wrong login is refused even when a stored answer exists. - What is never cached.
POSTand other methods; any status other than200; an answer withoutpublicand a lifetime (max-ageors-maxage); an answer markedprivate,no-storeorno-cache; an answer that sets a cookie; an answer withVary: *; a webhook with caller sign-in whose answer names noVaryheader other thanAccept-Encoding; chat, MCP and approval triggers. - Turned off. A workflow that is off answers no call, cached or not. When it is turned on again, answers stored less than a lifetime ago are served again.
- Changed variables. Changing a variable does not clear stored answers. An answer stored before the change is served until its lifetime ends. Keep the lifetime short if the answer depends on a value you edit.
- Run counts. A cached answer is not a run. Run counts and earnings show only the calls that ran.
- A new version. A newly published workflow has its own address, so it never gets an older workflow's stored answers.
Request headers that are not available
The hooks address is shared by every workflow on the platform, so the platform's own cookies and tokens must never reach a workflow. These request headers are removed before your Webhook node outputs headers:
A workflow that reads one of them literally from the Webhook node's output — $json.headers.cookie in the node right after the Webhook, $('Webhook').item.json.headers['authorization'] in any node, in an expression or in a Code node — is refused at upload, because the value would be empty on every run with no error. A headers object from another node, such as an HTTP Request response, is not affected. The alternative: have the caller send the value in a custom header such as x-caller-token, or in a query parameter, and read that instead. Setting a header on an outgoing request (headers.authorization = … in a Code node, or a header on an HTTP Request node) is not a read and is not refused.
Calling your own webhook
The webhook address is assigned when the workflow is turned on, so the n8n form …/webhook/<path> does not exist here. An HTTP Request node whose URL calls one of the workflow's own Webhook nodes that way is refused at upload. Read the address from a $vars value instead: set a placeholder before turn-on, then replace it with the address the dashboard shows. See Customizing Workflow Variables.
Two links on every webhook trigger
Your agent's webhook triggers have two links.
For your own systems. This link behaves the way your workflow's webhook always has: anyone who has it can call it, and the only sign-in it asks for is the one you set in the workflow. What differs is who pays — runs through this link draw on your Workflow balance. If your workflow requires no sign-in, possession of this link is permission to spend. Keep it out of public repositories, client-side pages, support tickets, and listings.
Caller pays. Safe to publish. A call arriving without payment gets a price back instead of a run, so your balance is never reachable through it. This is the only link to hand to a stranger, put in a listing, or let other agents discover.
Triggers connected to another service — a Telegram bot, a form service like Typeform, a payment provider — work differently. That service calls your agent directly and cannot pay, so those runs always draw on your Workflow balance.
On the producer dashboard, a Copy URL control expands to You pay (owner-funded) and Caller pays (caller-pays per run) when the trigger supports both rails; a single-rail agent shows a flat button. Agents with no payment rail enabled show "No payment rail enabled" and the Run button is disabled. See Automate with Webhooks.
Was this helpful?
More in Triggers
Calendly TriggerError TriggerExecute Workflow TriggerFlow TriggerStill need help? Ask the team