atteq API
  • Guides
  • Recipes
  • Cookbook
  • API Reference
Book an appointment without double-bookingSync a lead to the client's systemPush every call to a webhookGreet callers by nameQuote from a price list
powered by Zudoku
Recipes

Sync a lead to the client's system

Requirement

"Whenever the agent writes a lead, it must appear in our system."

Pieces

  • Custom data: a collection leads. The agent writes leads with its write_leads tool (write_tool_id on the collection), attached to a node the way any tool is.
  • Connection: the CRM's key, provider http_key.
  • One tool per action: an HTTP tool that posts one lead to the CRM.
  • Flow: read the record that was written, then post it.
  • Automation: trigger record_written on the collection, running the flow.

The shape to avoid

A tool the agent calls that writes the lead and posts it to the CRM. A lead written any other way, by an import, by a person, by another agent, never reaches the CRM; a CRM outage loses the lead entirely; and the key ends up on the tool. Here the collection is the record of truth and the automation is what keeps the CRM in step, whoever wrote the row.

Steps

Ids from an earlier step are written as {{step_N.field}}; the test that runs this page substitutes them.

  1. The CRM's key, once.

    create_connection

    Code
    { "provider": "http_key", "account_label": "CRM", "scopes": [], "grant_secret": "{\"header\":\"Authorization\",\"prefix\":\"Bearer \",\"value\":\"crm_live_replace_me\"}", "granted_via": "pasted" }
  2. The leads. Name, phone and email are personal, so the retention window applies to them.

    create_collection

    Code
    { "name": "Leads", "slug": "leads", "fields": [ {"key": "name", "label": "Name", "type": "text", "required": true, "personal": true}, {"key": "phone", "label": "Phone", "type": "text", "personal": true}, {"key": "email", "label": "Email", "type": "text", "personal": true}, {"key": "interest", "label": "Interest", "type": "text"} ] }
  3. One tool that posts one lead. The parameters become the JSON body.

    create_tool

    Code
    { "name": "push_lead", "description": "Create one lead in the CRM.", "source": "builtin", "config": {"endpoint": "https://crm.example.com/api/leads", "method": "POST"}, "input_schema": { "type": "object", "properties": { "name": {"type": "string"}, "phone": {"type": "string"}, "email": {"type": "string"}, "interest": {"type": "string"} }, "required": ["name"] }, "connection_id": "{{step_1.id}}" }
  4. The flow.

    create_flow

    Code
    {"name": "sync_lead"}
  5. A record_written run starts with collection_id, record_id and written_at, not with the record's values. So the entry step reads the record, with the collection's own get_leads tool: a collection with no identity field looks up by record_id.

    add_step (flow_id = {{step_4.id}})

    Code
    {"tool_id": "{{step_2.get_tool_id}}", "param_overrides": {"record_id": "$ref:trigger.record_id"}}
  6. Then post it. A get step answers {"record": {...}} with the record's fields, so each value is $ref:<step>.record.<field>.

    add_step (flow_id = {{step_4.id}})

    Code
    { "tool_id": "{{step_3.id}}", "param_overrides": { "name": "$ref:{{step_5.id}}.record.name", "phone": "$ref:{{step_5.id}}.record.phone", "email": "$ref:{{step_5.id}}.record.email", "interest": "$ref:{{step_5.id}}.record.interest" } }
  7. Read, then post.

    add_flow_edge (flow_id = {{step_4.id}})

    Code
    {"source_step_id": "{{step_5.id}}", "target_step_id": "{{step_6.id}}"}
  8. The trigger. Created switched off. min_interval_seconds is the floor between two runs of this automation.

    create_automation

    Code
    { "name": "Sync leads to the CRM", "trigger_kind": "record_written", "trigger_config": {"collection_id": "{{step_2.id}}"}, "flow_id": "{{step_4.id}}", "min_interval_seconds": 300 }
  9. Switch it on. Enabling has its own route on purpose: it is the consequential step.

    set_automation_enabled (automation_id = {{step_8.id}})

    Code
    {"enabled": true}

What the agent sees

Nothing new. The agent writes a lead with write_leads as before; the automation runs with no conversation, once per record written, and each run is listed by list_automation_runs with what it was handed and how it ended.

Variations

  • Only leads with a phone number: add a conditional edge between step 5 and step 6 with {"field": "<step 5 id>.record.phone", "op": "exists"}.
  • An update in the CRM rather than a create: a second HTTP tool with PUT, and a first HTTP tool that looks the lead up; the edge on whether it was found picks one.
  • The other direction, CRM to platform: an automation with trigger_kind: "webhook"; the CRM calls the webhook and the flow's entry step reads $ref:trigger.<key> from the delivered body, writing with write_leads.
Last modified on November 4, 2026
Book an appointment without double-bookingPush every call to a webhook
On this page
  • Requirement
  • Pieces
  • The shape to avoid
  • Steps
  • What the agent sees
  • Variations
JSON
JSON
JSON
JSON
JSON
JSON
JSON
JSON
JSON