Skip to main content

CRM & Webhooks

Connect your agents to customer data and external systems. The Get User tool hydrates the agent with caller identity at the start of every conversation and on demand mid-call. Webhooks push data out to any external service in real time.

Get User Tool

Get User pulls caller / visitor information from one or more sources, normalises it into a set of {{variable}} slots that substitute into your greeting + system prompt, and exposes the same data to the agent’s get_user() function for mid-conversation lookups.

Four sources, multi-select

You can enable any combination — they run in priority order and later sources can use earlier sources’ values.
SourceWhat it isOutbound?
URL (Web Embed)Reads variables from the embed URL’s query string or dispatch metadata (phone calls, outbound campaigns).No
WebhookPOSTs the caller’s identifier to your CRM endpoint; flattens the JSON response into variables.Yes (HTTP)
API (generic REST)Like Webhook, but you control the method, URL template, headers, and body. JSONPath response map projects nested fields.Yes (HTTP)
Database (your own Postgres)Runs a pre-approved SELECT against your own Postgres with bound parameters. Read-only, write keywords blocked, statement timeout enforced. Agents never write SQL.Yes (DB)
The Database source connects to your own Postgres only. ThinnestAI’s platform tables are not queryable from your agents — your data stays in your control.

Search By

You decide which fields the agent is allowed to use as the lookup identifier:
  • Name — agent passes the caller’s name to get_user("Sarah Johnson").
  • Contact Number — agent passes the caller’s phone.
  • Other — add as many labelled fields as you need (Customer ID, Email, Account Number, Loyalty Number, …). Each becomes its own searchable key.
These fields double as {{variable}} slots in your greeting and system prompt. Even if you don’t enable any DB/API/Webhook lookup, just declaring “Customer ID” in Search By + capturing it from the embed URL means you can write Hello {{customer_id}}! in your greeting.

Setting up the tool

  1. Navigate to your agent → ToolsAdd ToolGet User.
  2. Search By: tick Name and/or Contact Number, and add Other rows for any custom identifiers your CRM uses.
  3. Get User Details From: enable one or more sources. Click each source in the sidebar to configure it.
  4. Save.
The Get User modal opens as a wide sidebar+content view — pick Search By, the timing options, or any of the four sources in the left rail; the editor for the selected item fills the right pane.

When the lookup runs

The first item in the sidebar — Get Details / User Data — controls when enabled sources execute. Two independent toggles:
ToggleEffect
Before agent speaksPrefetch runs during connection setup. Resolved fields substitute into {{variables}} in your greeting + system prompt before the first word is spoken. Recommended for the “Hi Priya” greeting use case.
In call (by the agent)Exposes get_user() to the LLM so the agent can look users up mid-conversation. The agent decides when to call it based on your system prompt (e.g. “If the caller mentions a different account, call get_user with that customer ID.”).
Enable either, both, or neither — most setups want both on.

Source 1 — URL (Web Embed)

For the chat widget and try-voice-call panel, the embed URL’s query params travel with the conversation. For phone calls, the same field carries outbound-campaign custom variables. Config:
SettingDescriptionExample
Allowed keysComma-separated whitelist of param names to harvest. Leave empty to allow all.customer_id, email, plan
Default valuesJSON object of fallback values when a key wasn’t supplied.{"name": "there"}
Embed URL example:
https://yoursite.com/chat?customer_id=C-987&email=jane@x.com&plan=Pro
The agent now sees {{customer_id}} = C-987, {{email}} = jane@x.com, {{plan}} = Pro. Outbound campaign example: when you launch a campaign, each contact row’s custom variables (e.g. {"customer_id": "C-987", "plan": "Pro"}) ride along with the dispatched call and land in the same URL-source slot.

Source 2 — Webhook (CRM lookup)

Fires POST <your-url> with {"identifier": "<lookup>"} at conversation start. Response JSON’s top-level fields become {{variables}}. Config:
SettingDescriptionExample
URLYour CRM lookup endpointhttps://api.yourcrm.com/lookup
Auth header / valueSent with every request. Stored encrypted.Authorization: Bearer sk-...
Identifier paramJSON body key carrying the lookupphone, email, customer_id
Response mapOptional: project nested JSON into flat vars.{"name": "$.user.name", "plan": "$.user.plan"}
Request:
POST https://api.yourcrm.com/lookup
Authorization: Bearer sk-...
Content-Type: application/json

{ "identifier": "+15551234567" }
Response:
{
  "name": "Sarah Johnson",
  "email": "sarah@company.com",
  "account_type": "Enterprise",
  "account_status": "Active",
  "last_order": "2026-02-28"
}
Variables now available: {{name}}, {{email}}, {{account_type}}, {{account_status}}, {{last_order}}.

Source 3 — API (generic REST)

The full power of HTTP. Use this when your CRM expects a GET with the identifier in the URL, or a custom JSON body, or you need to call the same endpoint with different params per session. Config:
SettingDescriptionExample
URL templateSupports {placeholders} from earlier sources or {lookup}https://api.example.com/users/{contact_no}
MethodGET, POST, PUT, PATCH, DELETEGET
HeadersJSON map; values support {placeholders}{"X-Api-Key": "abc123"}
Body templateJSON template (POST/PUT/PATCH); supports {placeholders}{"phone": "{lookup}"}
Response mapJSONPath-style projection into {{vars}}{"name": "$.firstName", "tier": "$.subscription.tier"}
Placeholders available inside templates:
  • {lookup} — what the agent passed to get_user() (or the caller’s phone at conversation start)
  • {contact_no}, {email}, {customer_id}, {name}, … — anything supplied by earlier sources (URL, Webhook) or that the embed/dispatch provided
  • Missing placeholders collapse to empty string — your endpoint should tolerate it

Source 4 — Database (your own Postgres)

Connect the agent to your own Postgres database for direct lookups. The agent never writes SQL — you define query templates with named parameters, and the agent only supplies values. Security model:
  • Connection is opened read-only for every query.
  • Write keywords (INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, TRUNCATE, GRANT, REVOKE, COPY ... FROM, …) are rejected before the query reaches your DB.
  • Only a single SQL statement per template — stacked statements are blocked.
  • Statement timeout caps every query at 5 seconds.
  • Results are capped to 50 rows per call.
  • Values are bound as parameters — there is no string interpolation into SQL.
  • Credentials are stored encrypted in the platform.
Connection:
SettingDescription
Host, Port, Database, UserYour Postgres connection details
PasswordStored encrypted; leave blank on later edits to keep the saved value
SSL Modedisable, prefer, or require (recommended for cloud DBs)
Click Test Connection to verify the agent can reach your DB. Query templates: Each template binds the agent’s lookup arg to one of your Search By keys.
FieldDescriptionExample
IDIdentifier for the template (any slug)by_email
Search ByWhich Search By field this template bindsemail, contact_no, customer_id, … or any
ModeVisual (point-and-click) or SQL (write it yourself)
SQLSELECT only. Named bind syntax: %(field_name)sSELECT id, name, plan FROM users WHERE email = %(email)s LIMIT 1
Response mapProject columns onto {{variable}} names{"user_id": "id", "name": "name", "plan": "plan"}
Visual builder: if you’d rather not write SQL, switch to Visual mode. Pick the table from a searchable dropdown (populated from your DB’s information_schema — type to filter when you have hundreds of tables), multi-select columns with search + Select all, add WHERE conditions with operator + bound parameter, set ORDER BY + LIMIT. The generated SQL preview updates live and is validated against your DB on every change.

Bulk-add tables

If your schema has many tables and you want one query per table — for example, an agent that should be able to read from users, orders, wallets, subscriptions, and a dozen more — use the Bulk button next to + Add in the Queries list.
  1. Click Bulk → the right pane swaps to the bulk-add view.
  2. Search for tables and/or click Select all to grab everything visible (the toggle respects your current filter — type audit_ then click Select all to grab every audit_* table at once).
  3. (Optional) Configure Apply this WHERE to every query:
    • Column — autocompletes from the union of columns across every selected table (so picking 87 tables surfaces every column name you might want).
    • Op=, !=, LIKE, ILIKE, IN. Defaults to =.
    • Bind to — pick which Search By key to bind the value to (e.g. :email_id, :contact_no, :customer_id).
    • A live coverage badge tells you how many of the picked tables actually have the column you typed (green = all match, amber = partial, red = none).
  4. Click Add N queries — one query template gets created per selected table.
How the WHERE is applied:
Table has the column?Generated WHERE
YesWHERE "column" = %(bind)s is added, ready to run
NoEmpty WHERE — you fill it in per-query from the right pane
Each generated query starts on the visual builder, with the table set, columns empty (SELECT *), and the WHERE row populated where applicable. The first newly-added query is selected automatically so you land on something useful instead of staring at the sidebar. SQL example — search by email:
SELECT id, name, email, plan, last_login
FROM users
WHERE email = %(email)s
LIMIT 1
SQL example — search by user_id:
SELECT u.id, u.name, u.plan, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.id = %(user_id)s
GROUP BY u.id
LIMIT 1
When the agent calls get_user("jane@x.com") and the active template’s Search By is email, the value is bound to %(email)s automatically. You don’t need to plumb the agent’s input anywhere — it’s wired by the Search By config.

How the identifier reaches the lookup

Where the identifier comes fromWhat happens
Web embed — URL has ?email=jane@x.comURL source captures it. Available as {{email}} and as %(email)s bind. Zero outbound HTTP.
Phone — inbound, no campaignOnly the caller’s phone is known. Bind it to a query with Search By = contact_no. The agent can also ask the caller for more info mid-call.
Phone — outbound campaignThe campaign’s per-contact custom variables (any fields you stored) ride with the dispatch and land in URL-source slots.
Agent collects mid-conversationAgent asks for the info, then calls get_user("the_value"). Bound to whichever Search By matches the active query template.
Chained: URL gives partial data → DB enrichesURL source returns {customer_id: "C-987"}. Database source’s query WHERE customer_id = %(customer_id)s uses it.

Variables in greeting + system prompt

Every Search By field and every key in a source’s response map shows up in the {{ }} autocomplete inside your greeting and system prompt editors. Greeting:
Hello {{name}}! I see you're on our {{plan}} plan.
System prompt:
You are speaking with {{name}}, a {{plan}} customer (account: {{account_status}}).
Their customer ID is {{customer_id}}. Reference their {{last_order}} purchase
if relevant.
If a variable wasn’t supplied for this session, the placeholder stays as literal text (so configure Default values on the URL source for graceful fallbacks like {"name": "there"}).

Source chaining — concrete example

A bank wants the agent to greet the caller by name AND check their account balance. Setup:
  • Search By: Customer ID (key=customer_id), Contact Number
  • URL source: enabled, allowed_keys: customer_id
  • Database source: enabled, query template:
    • Search By: customer_id
    • SQL: SELECT name, account_status, current_balance FROM customers WHERE customer_id = %(customer_id)s LIMIT 1
    • Response map: name → name, status → account_status, balance → current_balance
  • Greeting: Hi {{name}}! Your current balance is ₹{{balance}} and your account is {{status}}.
Flow:
  1. Caller dials in. The phone number is on file in campaign_contacts, so the dispatcher attaches their customer_id to the call.
  2. URL source captures customer_id = "C-987".
  3. Database source runs WHERE customer_id = %(customer_id)s → returns name, status, balance.
  4. Greeting interpolates → “Hi Jane! Your current balance is ₹45,200 and your account is Active.”

Using lookup data in the conversation

Once Get User has populated the variables, the agent can reference them naturally in its instructions:
At the start of every conversation, the user's profile has already been
loaded.

If the customer is on the Enterprise plan, prioritise their request and
offer to escalate to their dedicated account manager.

If their account status is "Past Due", gently mention the outstanding
balance and offer to help resolve it.

Never read out the customer's full email address — just use their name.
Sample voice call:
[Incoming call from +1-555-123-4567]
[Get User: URL → customer_id="C-987"; Database → name="Sarah", plan="Enterprise"]

Agent:  "Hello Sarah! Thanks for calling. I see you're on our Enterprise
         plan. How can I help you today?"

Sarah:  "I need to upgrade our seat count."

Agent:  "Of course! You currently have 50 seats on your Enterprise plan.
         How many additional seats would you like to add?"

Custom Webhook Integration

Beyond CRM lookups, you can use webhooks to send data from your agent to any external system.

Sending Data to External Systems

Create a custom webhook tool that pushes data to your backend: Common webhook use cases:
Use CaseTriggerData Sent
Lead captureUser provides contact infoName, email, phone, interest
Support ticketUser reports an issueIssue description, severity, user ID
Order placementUser confirms an orderProduct, quantity, shipping info
Appointment bookingAgent books a slotDate, time, attendee details
Call summaryCall endsTranscript summary, action items, sentiment

Setting Up an Outbound Webhook

  1. Go to Tools > Add Tool > Custom Tool (API).
  2. Configure your webhook endpoint:
# Example: Create a support ticket
Endpoint: https://api.yourapp.com/tickets
Method: POST
Headers:
  Authorization: Bearer your-api-key
  Content-Type: application/json
  1. Define the parameters the agent will fill in:
ParameterTypeDescription
subjectstringTicket subject/title
descriptionstringDetailed issue description
prioritystringlow, medium, high, urgent
customer_emailstringCustomer’s email address
  1. The agent will call this webhook when it determines a ticket should be created.

Webhook Authentication

Secure your webhooks with one of these methods:
# Bearer token
Authorization: Bearer sk-your-webhook-secret

# API key header
X-API-Key: your-api-key

# HMAC signature (verify on your server)
X-Webhook-Signature: sha256=...

Webhook Response Handling

Your webhook should return a JSON response that the agent can use:
{
  "success": true,
  "ticket_id": "TKT-4521",
  "message": "Ticket created successfully"
}
The agent will use this response to confirm the action to the user:
Agent: "I've created a support ticket for you. Your ticket number is
        TKT-4521. Our team will get back to you within 2 hours."

Receiving Webhook Events

You can also trigger agent actions from external events by sending webhooks to the thinnestAI API.

Use Cases

  • New lead in CRM — Trigger the agent to send a welcome email.
  • Payment received — Notify the agent to update the customer’s account status.
  • Support ticket updated — Have the agent follow up with the customer.

Sending Events to thinnestAI

curl -X POST https://api.thinnest.ai/agents/{agent_id}/trigger \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "new_lead",
    "data": {
      "name": "Alex Rivera",
      "email": "alex@startup.com",
      "source": "website",
      "interest": "Enterprise plan"
    }
  }'

Example: Full CRM-Connected Voice Agent

Build a voice agent that looks up callers, handles requests, and logs everything to your CRM.

Agent Setup

FieldValue
NameCustomer Service Agent
ModelGPT-4o
ToolsGet User (CRM), Custom Webhook (Create Ticket), SMS

Agent Instructions

You are a customer service agent for Acme SaaS.

ON EVERY CALL:
1. Look up the caller using the Get User tool
2. Greet them by name and reference their account

HANDLING REQUESTS:
- For billing questions: look up their account and answer directly
- For technical issues: create a support ticket with full details
- For upgrade requests: explain the options and offer to process it
- For cancellations: understand why, offer alternatives, escalate if needed

AFTER RESOLUTION:
1. Create a webhook ticket summarizing the interaction
2. Offer to text them a summary or confirmation number via SMS
3. Thank them and ask if there's anything else

TONE: Professional, helpful, empathetic. Never argue with the customer.

Best Practices

CRM Integration

  • Cache responses — If your CRM lookup is slow, consider caching frequent lookups.
  • Handle missing users — Tell the agent what to do if the user isn’t found in the CRM (e.g., collect their info and create a new record).
  • Keep data fresh — CRM data can change. The agent fetches it live on each interaction.

Webhook Reliability

  • Return quickly — Your webhook endpoint should respond within 5 seconds. For longer operations, accept the webhook and process asynchronously.
  • Return meaningful errors — If something goes wrong, return an error message the agent can relay to the user.
  • Idempotency — Design your webhooks to handle duplicate calls gracefully.

Security

  • Validate webhook signatures on your server to ensure requests come from thinnestAI.
  • Use HTTPS for all webhook endpoints.
  • Limit data exposure — Only return the CRM fields your agent actually needs.

Webhook Tools (Zapier / Make.com / n8n)

Send data from your agent to any automation platform. Three tiles cover the common platforms:
ToolUse when
Webhook (generic)Custom endpoint or Zapier. See below.
Make.comTriggering a Make.com scenario. Dedicated tile with Make-specific setup docs.
n8nTriggering an n8n workflow (self-hosted or Cloud). Dedicated tile with n8n-specific setup docs including Header Auth.
All three share the same runtime behaviour (the agent POSTs a JSON payload with {event, source, data}). The separate tiles exist for discoverability and platform-specific config hints. You can use multiple simultaneously in one agent (e.g. one Make scenario + one n8n workflow).

Setting Up (generic Webhook)

  1. Go to Tools > Add Tool > Webhook.
  2. Paste your webhook URL from Zapier or any custom endpoint.
  3. Optionally name it (e.g., “Zapier CRM”) and add a webhook secret.
SettingDescriptionExample
Webhook URLYour automation endpointhttps://hooks.zapier.com/hooks/catch/123/abc/
NameFriendly name (optional)Zapier Lead Capture
SecretSignature verification (optional)whsec_...

What the Agent Can Do

The agent has two functions:
  • send_webhook(data, event_type) — Send any JSON data with an event type
  • send_event(event_name, customer_name, email, phone, notes) — Send structured CRM-style events

Example: Zapier Lead Capture

User: "My name is Rahul, email is rahul@company.com, I want a demo"

Agent: [Calls send_event]
  event_name: "new_lead"
  customer_name: "Rahul"
  customer_email: "rahul@company.com"
  notes: "Wants a demo of voice agents"

→ Zapier receives JSON, creates HubSpot contact + sends Slack notification

Payload Format

Every webhook receives this JSON structure:
{
  "event": "new_lead",
  "source": "thinnestai",
  "data": {
    "customer_name": "Rahul",
    "customer_email": "rahul@company.com",
    "notes": "Wants a demo of voice agents"
  }
}

Platform-Specific Setup

PlatformGet Webhook URLDedicated tile?
ZapierCreate a Zap → Trigger: “Webhooks by Zapier” → “Catch Hook” → Copy URLUse the generic Webhook tile.
Make.comCreate a Scenario → Add “Webhooks” module → “Custom webhook” → Copy URLYes — use the Make.com tile.
n8nAdd “Webhook” node → Copy Production URL (not the Test URL)Yes — use the n8n tile.

Composio (500+ App Integrations)

Connect your agent to 500+ SaaS apps using Composio — including CRMs (Zoho, Pipedrive, Freshsales), productivity tools, and more. Composio handles authentication and API complexity automatically.

Setting Up

  1. Sign up at composio.dev and get your API key.
  2. Go to Tools > Add Tool > Composio.
  3. Paste your Composio API key.
  4. The agent can now access any app you’ve connected in your Composio dashboard.

Supported Apps

Composio connects to 500+ apps including:
CategoryApps
CRMZoho CRM, Pipedrive, Freshsales, Salesforce, HubSpot
ProductivityGoogle Workspace, Notion, Slack, Asana, Linear
MarketingMailchimp, SendGrid, ActiveCampaign
SupportZendesk, Intercom, Freshdesk
FinanceStripe, QuickBooks, Xero

When to Use Composio vs. Direct Tools

ScenarioUse
You need HubSpot/Salesforce with deep customizationDirect tool (built-in)
You need Zoho/Pipedrive/FreshsalesComposio (no custom code)
You need 5+ different SaaS integrationsComposio (one API key for all)
You need simple webhook automationWebhook tool (Zapier/Make)

Built-in CRM Tools

thinnestAI includes direct integrations for popular CRMs. These are deeper integrations with full CRUD operations.
CRMOperationsSetup
HubSpotContacts, Deals, Companies, Tickets — create, update, searchHubSpot Access Token
SalesforceLeads, Accounts, Contacts, Opportunities — full CRUD + SOQLUsername + Password + Security Token
ActiveCampaignContacts, Deals, Lists — create, update, manageAPI URL + API Key
ApolloPeople search, Company search, Email finder, SequencesApollo API Key
LinkedInProfile lookup, Company search, Share postsLinkedIn Access Token
To add a CRM tool: Tools > Add Tool > select the CRM > enter your API credentials.

Next Steps

  • Custom Tools — Build more complex API integrations.
  • SMS — Send text confirmations and follow-ups.
  • Tools Overview — Browse all available tools.