CRM: amoCRM and Bitrix24
There are no ready-made “create a deal”, “find a contact”, “move it down the pipeline” actions here. Each CRM gets a pair of nodes — one that picks the connection and one universal call — and that call is a thin wrapper over the CRM’s REST API: you write which method to invoke and what to put in the body, while the platform takes care of the address, the auth header, the timeout and the JSON parsing. Which means the CRM’s own documentation stays open next to the editor: method names, field names and the shape of the response are theirs, not ours. That is the price of being universal — every method the CRM ships is available the day it ships, instead of waiting for us to write a node with “Deal title” and “Amount” fields.
What a connection stores, and what authenticates the call
Credentials live in the workspace rather than in the graph — the general mechanism is described in the integrations overview. What matters here is that the two CRMs do it differently.
| Connection kind | Fields | What authenticates the call |
|---|---|---|
| amoCRM | Subdomain + long-lived token | a Bearer header carrying the token |
| Bitrix24 | Incoming webhook URL (+ portal domain, optional) | the URL itself: the webhook code sits in its path |
amoCRM. The Subdomain field takes either a
bare account label (mycompany → mycompany.amocrm.ru) or a full host in one of three
allowed zones: amocrm.ru, amocrm.com, kommo.com. The zone list is closed, and not out of
caution: the value is interpolated into the address of a request that carries your token — an
arbitrary host in that field would mean a saved connection can walk the token anywhere. The
token is long-lived, issued in the integration settings of the amoCRM account itself; the
platform runs no OAuth refresh flow, so the token’s lifetime is yours to watch, and on the day
it expires every call starts answering 401.
Bitrix24. For an incoming webhook the credential is the entire URL —
https://<portal>.bitrix24.ru/rest/<user id>/<code>/ — there is no separate token. That is
why the field is stored as a secret and never handed back to the browser. A practical
consequence: the rights of a call are decided not by the platform but by whose account the
webhook was created under and with which scopes. An “insufficient permissions” reply is fixed
in Bitrix24, not in the workflow. The Portal domain (optional) field does not affect the call
at all — it is cross-checked against the webhook’s host when you save, and it catches exactly
one mistake: a webhook pasted from a different (or already regenerated) portal.
Two nodes per service
The shape is identical for both CRMs. The config node (amoCRM Config, Bitrix24 Config) does nothing but select a saved connection and hand it over a data wire into the calling node’s port (amoCRM REST Call, Bitrix24 REST Call). Execution order is set by the ordinary execution chain — the configuration plugs in from the side and does not affect it.
- Execute + Data
- Data
A second node for a single call is not compulsory: the calling node carries its own connection picker. The precedence rule is the same across the whole integration family — a wired port beats the field — and the form says so by dimming the field.
While neither is filled in, the editor shows a wire.required_port_not_wired warning, and
deploy and run reject such a graph (the codes are listed on
Validation codes). A filled-in field counts as a source just like
a wire does, so the warning disappears either way.
The config node’s output is redacted in the run log: only the connection’s name survives, without the token and without the webhook URL. Worth remembering when you stare at a run and cannot find the value — it is not lost, it is deliberately not written to the database.
How a request is composed
| Node | Fields | What goes in them |
|---|---|---|
| amoCRM REST Call | method / path / body | an HTTP verb from a list; a path starting with a slash; a JSON body |
| Bitrix24 REST Call | method / params | a dotted method name; a JSON object of parameters |
The difference is not cosmetic. amoCRM addresses things “the HTTP way”: GET /api/v4/leads,
POST /api/v4/contacts, PATCH /api/v4/leads/12345. The verb is picked from a fixed list
(GET, POST, PATCH, PUT, DELETE) — there is no free text there because there is nothing to
type. Bitrix24 addresses things “the RPC way”: the method name is dotted — crm.lead.add,
crm.deal.list, user.get — and fully determines what happens; the path and the verb are
assembled by the platform.
path, body, Bitrix24’s method and params are templated fields: {{ inputs.<port> }},
{{ nodes.<id>.output.<field> }}, {{ variables.<name> }} and {{ secret.<NAME>}} all work
there as everywhere else (see Templates and variables). That is exactly
how data gathered by earlier nodes gets in:
/api/v4/leads/{{ nodes.find_lead.output.result._embedded.leads[0].id }}
Three things people trip over the first time:
- the path starts with a slash. It is appended to the host verbatim, with no
normalisation:
api/v4/leadswithout the leading slash produces a nonsense address and an unhelpful error; - an empty body and
{}are not the same thing. The default value ofbodyis literally{}, and that is a non-empty JSON object — it will be sent as the request body. For a plainGET, clear the field completely, and then no body is sent at all. Bitrix24 is the reverse: emptyparamsbecome{}, because an argument-less method is perfectly normal; - invalid JSON fails the node, it does not silently send nothing. The error carries the
VALIDATIONcode and the parser’s line and column. Building JSON by string concatenation in a template is the surest way to meet it; assembling the object in a separate node and passing it whole —{{ nodes.build_params.output }}— is sturdier.
Reading the answer
Both nodes put the parsed JSON into the result field of their output, so downstream it reads
as:
{{ nodes.crm_call.output.result }}
What sits inside is up to the CRM. Bitrix24’s {"result": …, "time": …} envelope is
unwrapped by the platform: result holds the contents of that field, not the envelope. For
amoCRM there is nothing to unwrap — the response body is handed over as it is, with all of its
structure (_embedded.leads, _links, _page and the rest), so the path to a value can get
long. The easiest way to learn the shape is a run in the test panel: it shows every node’s
input and output in full (see Testing a workflow).
Where an error goes
The clients of both CRMs are built never to raise: a transport failure, a 401, a 404 and an “error inside the envelope” reply all fold into the same kind of result. The node is what raises — otherwise a call with a dead token would end in a green run and an empty value would travel down the graph.
What happens next depends on one checkbox in the node’s configuration:
Off by default: without it a failure fails the whole run.
- off (the default) — the node’s failure fails the whole run: it ends up
failed, and the nodes downstream never execute; - on and wired — control leaves along the error branch and the run stays successful. The
branch receives an object with
message(the author-facing text),error_code,failed_node_idandfailed_node_type. Branch onerror_code: it is a stable string, fit for comparison in anifnode.
One subtlety only practice reveals: amoCRM distinguishes kinds of failure, Bitrix24 does
not. amoCRM answers with an HTTP status, so a 401 becomes AUTH, a 404 becomes NOT_FOUND,
a 402 becomes BILLING. Bitrix24 (like VK below) answers 200 with the error inside the body,
which could only be told apart by its text, so its failures all arrive as PROVIDER_ERROR
with the specifics in message. If your branch must tell “no rights” from “no such object”,
for Bitrix24 you will be reading that text.
Who sees what: the workflow’s author gets our sentence plus the CRM’s verbatim words (in the log, in the error branch, in the test panel); a chat visitor gets only a neutral phrase derived from the code, with no provider internals. The full account is on Errors; run statuses are on Statuses.
The typical scenario: a request in chat → fields → a deal
The most common chain looks like this: a person writes free-form text in the chat, a model extracts fields out of it, a CRM call creates the object, and the CRM’s answer turns into a reply for the human.
- Execute + Data
- LLM
- Data
- Parse the message. The Structured Output LLM node returns JSON matching a schema you define — name, phone, what the person wants — rather than free text. It is the schema, not “ask the model to answer in JSON”, that makes the next step predictable.
- Assemble the parameters.
paramsholds the method’s object in the CRM’s own terms:{"fields": {"TITLE": "{{ nodes.fields.output.title }}", "NAME": "{{ nodes.fields.output.name }}"}}. The field names come from Bitrix24’s documentation; a portal with custom fields configured will also have codes shaped likeUF_CRM_…. - Call the method.
crm.lead.addfor a lead,crm.deal.addfor a deal. The answer is the id of the created object — handy if a comment or a task has to be attached with a second call. - Answer the human. The exit node holds the text with a link to the created object,
assembled from
{{ nodes.crm_call.output.result }}. - Decide what a failure means. For a customer’s request, failing silently is the worst outcome: turn the error output on and send an honest “we could not file it, but we have your contact” down that branch, plus a notification to yourself.
How such chains are built in general — branching, templates, composing the reply — is covered in Tutorial: logic and branching. There is no ready CRM template in the examples right now: none of the built-in templates touches a CRM, because a template has to work the moment it is installed and a CRM call cannot work without your portal and your token. The nearest starting point is the field-extraction template, with the call added on top.
VK and VK Teams: outbound messages only
The four nodes next to the CRM ones — VK Config and Send VK Message, VK Teams Config and Send VK Teams Message — are built the same way: a connection from the side, templated fields, the same error-output checkbox. But they do exactly one thing: send a text message.
| Node | The connection holds | Fields | What it returns |
|---|---|---|---|
| Send VK Message | access token + API version | peer_id, text | message_id |
| Send VK Teams Message | bot token (+ API base URL) | chat_id, text | message_id |
The practical consequence: both nodes make sense as a notification — “a request came in”,
“the nightly sync failed”, “a customer is waiting” — into a staff conversation, not as a
channel for talking to customers. For VK the peer_id encodes the recipient’s kind in the number
itself: a user is their id (12345), a community is negative (-98765), a group conversation
is 2000000000 plus the conversation number (conversation 5 → 2000000005). Getting it wrong
sends the message to the wrong recipient rather than failing — VK will faithfully deliver it to
whoever’s id you named. For VK Teams the chat identifier is a colleague’s email
(user@example.com) or a group chat id (681234567@chat.agent). The
API base URL (optional) field is only for those running Teams
on their own server; leave it empty for the cloud one.
What next
Integrations overview
Where credentials live and who may change them.
HTTP request
The same idea for a service with no node of its own.
Errors
Codes, the on_error branch and what the customer sees.
Templates and variables
How data reaches path, body and params.
Tutorial: logic and branching
Build the whole chain, not one node.
Node reference
Ports and fields of all four CRM nodes.