How to Connect a Self-Hosted Stack With n8n and Webhooks
Your self-hosted CRM, helpdesk, newsletter and form tools each hold a piece of the customer record. Here is how to connect them with n8n webhooks and API calls, with real event names, working flows and honest costs.

You self-hosted Twenty for CRM, Chatwoot for support, Listmonk for email, Formbricks for surveys and Cal.com for bookings. Each one works. None of them talk to each other, so a new support conversation never reaches the CRM, a closed deal never joins the newsletter list, and your team re-enters data by hand. The fix is not another SaaS subscription. It is a webhook and API wiring layer, and n8n running on the same server is the practical way to build it.
This guide shows the exact events each tool emits, the API calls each one accepts, and four working flows you can build in an afternoon, with honest costs and the mistakes that bite people first.
Five good tools, five islands of data
The appeal of the self-hosted stack is ownership: your customer data sits in your databases on your server, and the monthly bill is a VPS line item instead of five per-seat SaaS subscriptions. We covered the money side in the real cost of replacing SaaS with an open source stack.
The weakness shows up a month later. Every record that should move between tools moves by hand instead:
- A customer emails support. The conversation lives in Chatwoot. The sales rep working that account in Twenty never sees it.
- A deal closes in Twenty. The customer should join the onboarding sequence in Listmonk. Someone exports a CSV instead, weekly, when they remember.
- A Formbricks survey comes back with a score of 3 out of 10. Nobody acts on it because nobody saw it.
- A prospect books a call in Cal.com. The CRM record has to be created before the call, from the confirmation email.
Zapier solves exactly this problem for SaaS tools, and it works with some of these apps. But look at the pricing model before you commit: Zapier's free plan allows 100 tasks a month and two-step Zaps, and webhooks and multi-step Zaps require a paid Professional plan. Every successful step in a Zap consumes a task, so a five-step flow running 2,000 times a month burns 10,000 tasks. You escaped per-seat pricing and walked into per-task pricing.
Why n8n, and why self-hosted
n8n is an open-source workflow automation tool. The model that matters here is the Webhook trigger node: n8n generates a URL, an app sends an event to that URL, and a workflow runs. The workflow can then call any HTTP API, transform the data in between, and branch on conditions.
Three facts make it the right glue for this particular stack:
- There is a free self-hosted Community Edition. The n8n pricing page lists a standard self-hosted version on GitHub at no cost. Paid plans add team features: the Business plan at 667 EUR a month is self-hosted only and buys SSO, LDAP, queue mode and Git version control. Cloud hosting starts at 20 EUR a month. For a single-stack wiring layer, the Community Edition is enough.
- n8n charges per execution, not per step. A workflow with eight nodes costs one execution when it runs. Zapier counts each step as a task. The gap grows with workflow complexity.
- It can sit next to your other containers. If Chatwoot and Twenty already run on your VPS, n8n is one more Docker service. Outbound API calls to your other apps can use internal network addresses that never touch the public internet.
One technical detail worth knowing before you start: the n8n Webhook node has separate test and production URLs. The test URL only listens while you click the test button in the editor. The production URL only works after you publish the workflow. Every "my webhook does nothing" story starts here. The node also supports Basic, Header and JWT authentication on incoming requests, and the default payload limit is 16MB, adjustable on self-hosted installs with the N8N_PAYLOAD_SIZE_MAX environment variable.
The wiring pattern: webhooks out, API calls in
Every flow in this guide follows one pattern, and it helps to see it once:
- An app emits a webhook event. Chatwoot, Twenty, Formbricks and Cal.com can all POST JSON to a URL when something happens.
- n8n receives it on a Webhook node. This is the trigger.
- n8n filters and transforms. Most events should not run the whole flow. An IF node checks the event type or payload before anything else happens.
- n8n calls the target app's API. Create a person in Twenty, add a subscriber in Listmonk, post a note in Chatwoot.
The direction matters. Webhooks push events out of the source app in real time. API calls pull or write data on demand. You need both, because no tool in this stack pushes events in exactly the shape the next tool wants.
A note on inbound URLs: the emitting app must be able to reach your n8n webhook URL. If both apps run on the same private network, an internal address works and the traffic never leaves your server. Cal.com is the exception worth flagging: the Cal.com webhook documentation notes that the SaaS version blocks private IP addresses and localhost for subscriber URLs, while a self-hosted Cal.com instance accepts HTTP and private IPs for internal webhooks. If you use Cal.com cloud, your n8n endpoint must be public HTTPS.
Flow 1: new Chatwoot conversation creates a person in Twenty
The most valuable sync in the stack: support context reaching the CRM automatically.
What Chatwoot sends
Chatwoot fires webhooks on conversation, message and contact events. The Chatwoot webhook guide lists the payload for each: conversation_created, conversation_updated, conversation_status_changed, message_created and typing events. The conversation_created payload includes the contact's name, email and the inbox it arrived on. You subscribe to the events you want, either in the UI under Settings, Integrations, Webhooks, or through the Chatwoot API with a subscriptions array.
What Twenty accepts
Twenty has a workflow webhook trigger built in. Per the Twenty docs on webhook triggers, you create a workflow in Settings, Workflows, pick the Webhook trigger, and Twenty gives you a unique URL. You then define the expected body by pasting sample JSON, and Twenty turns those fields into variables you can map to actions like Create Record on the People object.
The build
- In Twenty, create a workflow with a Webhook trigger. Paste a sample Chatwoot payload as the expected body: name, email, and the conversation ID. Save, and copy the webhook URL.
- Add a Search Records action on People, matching the email. Branch: if found, update the record; if not found, create it. This prevents a duplicate contact every time an existing customer opens a new conversation.
- Add a Create Record action for a Note on the person, containing the first message text and a link back to the Chatwoot conversation.
- Activate the Twenty workflow and test it with curl before touching Chatwoot.
- In Chatwoot, add a webhook pointing at the Twenty URL, subscribed only to
conversation_created.
One catch: as of the current docs, authentication on the Twenty inbound webhook trigger is listed as coming soon. Anyone who guesses the URL can create records. Keep the URL unguessable, treat it as a secret, and re-check the docs after upgrades. If that exposure is unacceptable, put n8n in the middle instead: Chatwoot posts to an authenticated n8n webhook, and n8n calls the Twenty API with an API key. Same result, one extra hop, proper authentication.
Flow 2: closed-won deal in Twenty subscribes the customer in Listmonk
This one runs in the opposite direction: Twenty emits the event, Listmonk receives the API call.
What Twenty sends
Twenty's outbound webhooks are configured in Settings, APIs and Webhooks. The Twenty webhooks documentation is clear about two things that shape your build. First, the event names follow an object.action pattern: person.created, company.updated, opportunity.updated. Second, there is no event filtering on the Twenty side. Every event for every object goes to your URL, and filtering "may be added in future releases."
That means your n8n workflow will receive a flood of events and must filter immediately. An IF node checking event equals opportunity.updated, and the stage field equals your won stage, drops everything else before it costs you anything.
What Listmonk accepts
Listmonk has a full REST API for subscriber management. The Listmonk subscribers API docs show POST /api/subscribers to create a subscriber and PUT /api/subscribers/lists to modify list memberships. Authentication is HTTP basic auth with an API user and access token pair, which n8n's HTTP Request node supports natively:
curl -u 'api_user:access_token' -X POST \
'https://listmonk.example.com/api/subscribers' \
-H 'Content-Type: application/json' \
-d '{"email":"jane@customer.com","name":"Jane Mwangi","lists":[4],"status":"enabled"}'
If you use double opt-in, create the subscriber with the unconfirmed status and call POST /api/subscribers/{id}/optin to send the confirmation email instead of enabling them directly. Compliance habits are easier to keep when the automation enforces them.
The build
- In n8n, create a workflow with a Webhook trigger using Header auth. Copy the production URL.
- In Twenty, create a webhook pointing at that URL.
- First node after the trigger: an IF that passes only
opportunity.updatedevents where the new stage is won. - Second step: search the Twenty API for the person attached to the opportunity to get their email and name, since the opportunity payload does not include them.
- Third step: HTTP Request to Listmonk, create or update the subscriber on your customers list.
- Add an error branch that posts failures to a Slack channel or an admin inbox. Silent failure means customers who paid you never get onboarding email.
While you are in the Listmonk docs, note the bounce webhook API: POST /webhooks/bounce records bounce events, and listmonk can receive bounces directly from SES, Sendgrid, Postmark and others on dedicated endpoints. For SES sending, the docs recommend a hard bounce count of 1 with a blocklist action, wired through an SNS HTTPS subscription. Deliverability automation belongs in the same project as subscriber sync, not in a later one.
Flow 3: Formbricks survey response creates a CRM note and alerts support
Formbricks is the easiest integration in the stack because n8n ships a built-in Formbricks trigger node. The Formbricks self-hosting docs for n8n walk through it: add the Formbricks trigger, create credentials with your instance host and an API key, pick the event and the surveys to watch.
The event you want for a feedback loop is Response Finished. Keep the scope narrow: select only the surveys that need follow-up, typically your NPS or post-onboarding survey, not every form you have ever published. The trigger needs one real test response in the survey before you can map its fields, so submit a throwaway response while building.
The workflow: Formbricks trigger, then an IF on the score. Low scores create a note on the matching person in Twenty via the API and open a conversation in Chatwoot so a human follows up. High scores can go straight to a Listmonk list of potential case-study candidates. One survey, three systems updated, nobody copying answers between tabs.
Flow 4: Cal.com booking created syncs to Twenty
Cal.com has the richest event menu in the stack. The Cal.com webhook docs list more than twenty triggers, including Booking Created, Booking Cancelled, Booking Rescheduled, Meeting Started, Meeting Ended and no-show updates. Webhooks can be scoped to a user or to individual event types, and you can set a secret to verify payloads plus up to ten custom headers, which are stored encrypted.
The flow is a smaller version of Flow 2:
- n8n Webhook trigger with Header auth.
- Cal.com webhook at
/settings/developer/webhooks, trigger Booking Created, subscriber URL is the n8n production URL, secret set. - In n8n, verify the secret, then upsert the person in Twenty by email and create an Activity or Note with the meeting time.
- Optionally add a second branch on Booking Cancelled that updates the same record.
The step people skip is payload verification. The booking payload contains names, emails and sometimes phone numbers. A shared secret costs two minutes and proves the POST actually came from your Cal.com instance.
A worked cost example
Take a modest but realistic volume: six active flows, 2,500 events a month across them, four billable steps per flow on average. These are illustrative numbers, not benchmarks.
- On Zapier: 2,500 events multiplied by four steps is 10,000 tasks a month. Webhooks and multi-step Zaps require the Professional plan, and 10,000 monthly tasks puts you in a mid task tier. Pricing is per task because each successful step consumes from your task allowance.
- On n8n cloud: the same load is 2,500 executions, because n8n bills when a workflow runs start to finish, regardless of how many nodes it contains. Cloud plans start at 20 EUR a month billed annually.
- On self-hosted n8n Community Edition: the software cost is zero. The incremental infrastructure cost is close to zero too if n8n shares the VPS you already run for the stack, which it can for this volume. Your real cost is the half day of setup and an hour of maintenance a month.
The break-even question is not really n8n versus Zapier on price. It is whether you will maintain a workflow tool at all. If the answer is no, pay Zapier and stop reading. If you already maintain a self-hosted stack, adding n8n to it is a small step, and our posts on monitoring a self-hosted business stack and backing up a self-hosted stack and proving it restores cover the operational habits that make it boring.
Common mistakes
- Using the n8n test URL in production. The test URL only listens while you are clicking in the editor. Wire up the production URL and publish the workflow, or the flow silently never runs.
- Subscribing to every Chatwoot event.
message_createdfires on every message in every conversation. A busy inbox can bury your workflows. Subscribe to the two or three events you actually use. - Forgetting that Twenty sends all events to one URL. No server-side filtering exists yet. Filter in the first n8n node or pay for thousands of junk executions.
- Triggering Twenty workflows on Record is Created for manually entered records. The Twenty workflow docs warn that the UI autosaves, so the workflow fires before the user finishes typing. Use Record is created or updated for manual data entry.
- Creating loops. Twenty updates a record, n8n hears the webhook, n8n writes back to Twenty, which fires another webhook. Break loops by tagging automation-written records with a source field and filtering it out, or by comparing
changed_attributesbefore acting. - No authentication on webhook endpoints. An open webhook URL is a write API into your CRM. Use Header or Basic auth on every n8n webhook node, set the Cal.com secret, and keep the Twenty inbound URL private until its auth feature ships. n8n also ends up holding API credentials for every tool in the stack, so its editor login deserves the same treatment: our guide to single sign-on for a self-hosted stack with authentik covers putting one identity provider in front of all the admin UIs.
- No failure handling. Webhook delivery is mostly fire-and-forget, and none of these tools guarantee replay. If n8n is down when Chatwoot fires, the event is gone. Add error workflows in n8n, alert on failures, and for critical flows add a scheduled reconciliation run that compares systems nightly.
When n8n is the wrong choice
Be honest about the alternatives, because sometimes they win:
- One simple two-step flow. If all you need is "new Cal.com booking sends a Slack message," Zapier's free tier or even Cal.com's built-in integrations do it with zero maintenance. Self-hosting a workflow tool for one flow is over-engineering.
- Twenty-internal automation. Twenty's own workflows handle record-created logic, scheduled runs and HTTP requests without any external tool. If both ends of the automation live in Twenty, stay in Twenty.
- Deep SaaS ecosystems. If your stack is Salesforce, HubSpot and Slack rather than open-source tools, the native integrations and Zapier's app directory will beat anything you wire by hand. n8n's advantage here comes from the self-hosted stack's open APIs and your ownership of the network path. If you are on HubSpot today and weighing the move, our HubSpot to Twenty CRM migration guide covers the data side of that decision before you wire anything.
- No operator. These flows fail silently when nobody watches them. If nobody on the team will own the workflows, pay for a managed automation tool or a managed stack instead.
Frequently asked questions
Is n8n really free if I self-host it? The Community Edition on GitHub is free to run, and it covers everything in this guide: webhooks, HTTP requests, scheduling, error workflows. Paid plans add collaboration and enterprise features like SSO, queue mode and Git-based version control, and the Business tier at 667 EUR a month is itself self-hosted only. You pay with your time and your server, not with a license.
Do the apps need public URLs to talk to each other? Only the receiving side needs to be reachable by the sending side. Apps on the same Docker network or private subnet can use internal addresses, which keeps traffic off the public internet. The exception is SaaS emitters: Cal.com cloud refuses private IPs and localhost for webhook URLs, so a cloud-hosted Cal.com requires a public HTTPS n8n endpoint.
Can Twenty replace n8n for this? For automation that starts and ends inside Twenty, yes: its built-in workflows handle record triggers, schedules and even inbound webhooks. For flows that span Chatwoot, Listmonk, Formbricks and Cal.com, you need a tool that speaks to all of their APIs with branching logic. That is n8n's job.
What happens when n8n is down and a webhook fires? The event is usually lost. Chatwoot, Twenty and Cal.com do not offer guaranteed replay of missed deliveries. Mitigate with uptime monitoring on n8n itself, n8n error workflows that alert you, and a scheduled reconciliation flow for anything revenue-critical, like the closed-won to Listmonk sync.
How do I avoid creating duplicate contacts? Never create blind. Every flow in this guide searches the target system first, by email, and branches between update and create. Email is the natural dedupe key across all five tools. The one exception is systems where a person can legitimately have several addresses, in which case store the source system's ID in a custom field and match on that instead.
Should the newsletter opt-in be automatic when a deal closes? Only if your terms and your jurisdiction allow it. The safer default is to add the subscriber as unconfirmed and use listmonk's optin endpoint to send a confirmation email. The automation is identical either way; one field changes.
Where to go from here
Pick one flow, the Chatwoot to Twenty sync is the best first candidate, build it with the test URLs, and run it for a week before adding the next. If you would rather have the whole stack deployed, wired and maintained for you, that is exactly what our managed open-source stack service does, and our business automation service covers the n8n workflows on top of it. Either way, the pattern in this guide is the one to follow: webhooks out, API calls in, filter early, and authenticate everything.
Related reading
About AI Agents Plus Editorial
AI automation expert and thought leader in business transformation through artificial intelligence.



