Back to Nodes

ACA

Last updated Aug 25, 2026

Manage contacts, lead lists, email sequences and conversations in ACA (Automated Client Acquisition), and trigger workflows from ACA events

80 Weekly Downloads
1,392 Monthly Downloads

Included Nodes

ACA
ACA Trigger

Description

n8n-nodes-aca

An n8n community node for ACA – Automated Client Acquisition.

ACA is a multi-tenant outbound platform: CRM contacts, lead lists, email sequences, and a unified inbox across LinkedIn, email, WhatsApp, Instagram, Telegram and SMS. This package gives you two nodes:

  • ACA – read and write contacts, lists, sequences, enrollments and conversations, and act on them.
  • ACA Trigger – start a workflow when something happens in ACA – including buying signals, with the signal attached – with signature verification.
  • Installation | Credentials | ACA node | ACA Trigger | Example workflow | Things worth knowing

    Installation

    n8n Cloud – Settings > Community nodes > Install, then enter n8n-nodes-aca.

    Self-hosted – the same screen, or from the command line:

    npm install n8n-nodes-aca
    

    Restart n8n afterwards if you installed manually.

    Credentials

    The node authenticates with an ACA API token.

    1. In ACA, open Settings > CLI Tokens.
    2. Create a token. It is shown once – copy it then.
    3. In n8n, add an ACA API credential and paste it into API Token.
    4. Click Test. A green check means the token is valid and reaches your organisation.

    A token is tied to one organisation at the moment you issue it. Switching organisations in the ACA app later does not change what an existing token can see, so if you automate more than one organisation, create one token per organisation and one n8n credential per token.

    Tokens are stored as a hash and cannot be recovered. If you lose one, revoke it and issue another.

    Rate limit: 120 requests per minute per token. Every response carries X-RateLimit-Remaining; exceeding it returns 429 with a Retry-After.

    ACA node

    | Resource | Operations |
    | — | — |
    | Contact | Get, Get Many, Create, Update, Delete, Delete Many |
    | Lead List | Get, Get Many, Create, Archive, Get Contacts, Add Members, Remove Members |
    | Sequence | Get, Get Many |
    | Enrollment | Get, Get Many, Create (enroll contacts or whole lists) |
    | Conversation | Get, Get Many, Get Messages |
    | Message | Send (reply into an existing conversation) |
    | Custom Field | Get Many, Create |
    | Event Action | Add Tag, Remove Tag, Add Note, Change Stage, Update Score, Send Message, Enroll in Sequence, Remove From Sequence, Set AI Handling, Assign to User |

    Get Many operations support Return All, which follows ACA’s cursor pagination for you, and a Filters collection that maps to the API’s own filters.

    Filtering a lead list

    Lead List > Get Contacts carries the full filter set, so you narrow the list on
    ACA’s side rather than pulling everything and filtering in the workflow.

    The three switches people reach for most – Has Email, Has LinkedIn URL,
    Has Phone – are shortcuts over one general mechanism. Field Conditions
    exposes it directly: pick any contact field and ask whether it is filled,
    empty, equals or contains a value. Conditions combine with AND.

    Has LinkedIn URL: true
    Field Conditions: company contains "recruit"
    Email Quality:    clean
    

    Also available: search across name/email/company/title, countries, cities,
    industries, company sizes, seniorities, departments, detected and excluded
    technologies, ICP and decision-maker score ranges, and AI analysis status.

    These run through the same Advanced Filters engine the ACA app uses, so a filter
    means the same thing in both places.

    Event Action vs the REST resources

    They overlap on purpose, and the difference matters:

  • Add Tag under Event Action is additive. Contact > Update replaces the entire tag list, so adding one tag that way means reading the contact first and writing every tag back.
  • Event Action can target a contact by email or phone, not only by ID – useful when the workflow’s trigger gave you an address and nothing else.
  • Add Note, Set AI Handling and Assign to User have no REST equivalent at all.
  • ACA Trigger

    Pick the events you care about and activate the workflow. The node registers its own webhook subscription with ACA, and removes it when the workflow is deactivated – there is nothing to paste into ACA’s settings.

    Events

    | Event | Fires when |
    | — | — |
    | contact_created | A contact is created |
    | contact_updated | Any field on a contact changes |
    | stage_changed | A contact moves to a different pipeline stage |
    | score_changed | A contact’s lead score changes |
    | message_received | An inbound message arrives, on any channel |
    | message_sent | An outbound message goes out |
    | tagadded / tagremoved | A tag is added to or removed from a contact |
    | sequence_completed | A lead reaches the end of an email sequence |
    | lead_replied | An enrolled lead replies, and the sequence stops for them |
    | handoff_requested | A human takes over a conversation from the AI |
    | listmemberadded | A contact is added to a lead list |
    | listmemberremoved | A contact is removed from a lead list |
    | signalleadadded | A buying signal puts a lead on a lead list — see Buying signals |

    Narrowing to one lead list

    Select listmemberadded or listmemberremoved and a Lead List Names or IDs picker appears. Pick one or more lists and only those fire the workflow; leave it empty for every list.

    The filter is registered with ACA, not applied in n8n – events for the lists you did not pick are never queued and never delivered, so a 250,000-contact build into some other list costs your workflow nothing.

    Every list event carries the list it happened in, so you can branch on it too:

    {{ $json.data.list_name }}   // "Q3 SaaS founders"
    {{ $json.data.list_id }}
    

    Output

    By default the node emits a flattened shape:

    {
      "event": "lead_replied",
      "timestamp": "2026-08-11T12:56:42.801Z",
      "deliveryId": "6b1f0c2a-9d3e-4f58-a71b-2c8d4e5f6a7b",
      "organizationId": "afb808d4-0000-0000-0000-000000000000",
      "data": { "...": "event-specific" }
    }
    

    Turn on Options > Raw Envelope to get ACA’s delivery body verbatim instead.

    Delivery guarantees

    Delivery is at-least-once. ACA retries a failed delivery up to three times, roughly a minute apart, reusing the same X-Webhook-ID – surfaced as deliveryId. If your workflow does anything that must not happen twice, deduplicate on that value.

    Every delivery is signed. The node verifies X-Webhook-Signature (HMAC-SHA256 of the raw body) against the secret ACA issued when the subscription was created, and rejects anything that does not match with a 401. An event you did not select is acknowledged with a 200 and starts nothing, so ACA does not retry it.

    Buying signals

    Select the signalleadadded event and the workflow starts when a buying signal puts a lead on a lead list — a funding round, an acquisition, a hiring spike, a leadership move. One execution per lead, with the signal that caused it attached.

    It covers every way a signal becomes a lead in ACA: public-signal auto-enroll rules, your own custom signal subscriptions, and pushes from the signal library. signal_source tells you which one.

    A Signal Types picker appears with the event, limiting the workflow to funding, hiring, hiring demand, acquisition, job change, growth or executive hire. Leave it empty for all of them. Unlike the lead-list filter, this one runs inside n8n — ACA still queues and delivers the event, the node acknowledges it with a 200 and starts nothing. Use it for readability, not to save ACA work.

    Signal events are the one exception to the output shape above: the signal is flattened to the top level, so there is no data to reach through:

    {
      "event": "signalleadadded",
      "timestamp": "2026-08-13T07:24:11.380Z",
      "deliveryId": "6b1f0c2a-9d3e-4f58-a71b-2c8d4e5f6a7b",
      "organizationId": "afb808d4-0000-0000-0000-000000000000",

    "signalsource": "publicsignal_auto", "signal_type": "funding", "signal_date": "2026-08-12", "signal_title": "MarginEdge Raises $80M in Series D and Debt Funding", "signal_detail": "MarginEdge Raises $80M in Series D and Debt Funding", "whyitmatters": "MarginEdge just raised $80M: new budget and pressure to grow pipeline.", "signalsourceurl": "https://www.finsmes.com/...", "amount": "$80M", "investors": "Union Square Ventures, Osage Venture Partners",

    "contact_id": "70b72a7b-...", "contact_name": "Adam Booth", "contact_email": "adam@marginedge.com", "contactlinkedinurl": "https://linkedin.com/in/adamjbooth", "job_title": "Director of Revenue Operations",

    "company": "MarginEdge", "company_domain": "marginedge.com", "company_website": "https://marginedge.com", "company_linkedin": "https://www.linkedin.com/company/marginedge", "company_industry": "Food and Beverage", "companyemployeerange": "1001-5000", "companyrevenuerange": "Above $50M",

    "list_id": "62ec6931-...", "list_name": "Funding signals — Q3", "member_id": "68c4d70a-...", "added_at": "2026-08-13T07:24:11.380Z",

    "discovery_metadata": { "...": "the stamp ACA's own email writer reads" } }

    Empty fields are omitted, not null. A custom-signal lead carries less than a public-signal one, so guard with {{ $json.amount ?? '' }} rather than assuming a key exists.

    discoverymetadata is passed through verbatim. It is the object ACA’s sequence generator reads to resolve {{aisignal_*}} tokens, so you can hand it straight back to Contact > Update on a contact you create elsewhere.

    > Until 0.5.0 this event had its own node, ACA Signal Trigger. n8n allows one trigger node per service, so it was folded into ACA Trigger — same event, same filter, same output shape. If you used the old node, select signalleadadded here and everything downstream keeps working.

    Example workflow

    Reply to warm leads and tag them, then hand the conversation to a human.

    {
      "name": "ACA - handle replies",
      "nodes": [
        {
          "parameters": { "events": ["lead_replied"] },
          "type": "n8n-nodes-aca.acaTrigger",
          "typeVersion": 1,
          "position": [0, 0],
          "id": "trigger",
          "name": "ACA Trigger",
          "credentials": { "acaApi": { "id": "1", "name": "ACA account" } }
        },
        {
          "parameters": {
            "resource": "eventAction",
            "action": "add_tag",
            "targetBy": "contact_id",
            "target": "={{ $json.data.contact_id }}",
            "tagName": "replied"
          },
          "type": "n8n-nodes-aca.aca",
          "typeVersion": 1,
          "position": [220, 0],
          "id": "tag",
          "name": "Tag as replied",
          "credentials": { "acaApi": { "id": "1", "name": "ACA account" } }
        },
        {
          "parameters": {
            "resource": "eventAction",
            "action": "add_note",
            "targetBy": "contact_id",
            "target": "={{ $json.data.contact_id }}",
            "noteContent": "={{ 'Replied to ' + $('ACA Trigger').item.json.data.sequence_name }}"
          },
          "type": "n8n-nodes-aca.aca",
          "typeVersion": 1,
          "position": [440, 0],
          "id": "note",
          "name": "Log the reply",
          "credentials": { "acaApi": { "id": "1", "name": "ACA account" } }
        }
      ],
      "connections": {
        "ACA Trigger": { "main": [[{ "node": "Tag as replied", "type": "main", "index": 0 }]] },
        "Tag as replied": { "main": [[{ "node": "Log the reply", "type": "main", "index": 0 }]] }
      }
    }
    

    Things worth knowing

    These are ACA API behaviours that will bite you if you assume otherwise. The node surfaces each of them as inline help too.

    Creating contacts skips duplicates silently. With Dedupe on Email on (the default), an existing email is counted in skipped and its ID is not in contact_ids. A “create then add to list” chain therefore drops everyone who already existed. Look contacts up by email afterwards if you need every ID.

    Updating a contact merges custom fields, but replaces tags. Contact > Update
    exposes every updatable field individually, and its Custom Fields collection
    picks keys from a dropdown of your organisation’s definitions – values are merged,
    so fields you do not list keep what they had, and an empty value removes one.
    tags is the exception: it still replaces the whole list, so use
    Event Action > Add Tag to add one without disturbing the rest.

    Smart lists have no stored members. A smart list is a saved filter that ACA
    evaluates when you open it, so Lead List > Get Contacts returns nothing for one
    however full it looks in the app. The list picker labels them. Use a manual,
    imported or pool-built list.

    Lead lists cannot be deleted over the API. Use Archive instead. This is deliberate on ACA’s side – a deleted list would orphan enrollments that reference it.

    Enrolling nothing is a normal outcome. Enrollment > Create returns counts, not a success flag: enrolled, skipped, noEmail, suppressed and skippedActiveElsewhere. A lead already active in another sequence is skipped by default, because ACA will not let two sequences email the same person at once. Branch on enrolled, not on the absence of an error.

    List membership events are per contact. listmemberadded fires once per
    row, so a bulk add or a pool build produces one delivery per contact. It costs
    nothing when nobody subscribes, but subscribe on an organisation that builds
    large lists and expect proportionally large bursts. Set the lead list filter and
    the burst is confined to the lists you actually picked.

    contactupdated is noisy. It fires on every row change, so a bulk edit or an enrichment run produces a large burst. If you want meaningful transitions, subscribe to stagechanged or score_changed.

    Pagination is cursor-based and unordered by date. There is no offset and no total count – the underlying tables run to millions of rows. Return All walks the cursors for you.

    A failed Event Action returns 400. An unknown contact, an enrollment that matched nothing, a missing field – all 400 with a message saying which. Only a completed action returns 200.

    Compatibility

    Tested against n8n 1.x with n8n-workflow 2.x. No runtime dependencies.

    Building workflows with Claude

    This repo ships a Claude skill that teaches
    Claude how to build n8n workflows with this node – the exact parameter names, the
    patterns worth copying, and the ACA behaviours that decide whether a workflow is
    correct rather than merely valid.

    Copy the folder into your skills directory:

    cp -r skills/aca-n8n-workflows ~/.claude/skills/
    

    Then ask for what you want – “build me an n8n workflow that tags every lead who
    replies and notifies Slack” – and it will use the right operations, know that
    enrolling nothing is a normal outcome, and not reach for a smart list expecting
    members.

    Resources

  • ACA API reference
  • n8n community nodes documentation

License

MIT