AI agents·Updated May 5, 2026 · 10 min read

Connect your AI agent to external APIs with webhooks

Step-by-step guide to setting up webhook agent actions to retrieve and post data to your CRM, pricing engine, Shopify, or any HTTP API.

A webhook action lets your AI agent talk to systems outside Chatlane. When the agent decides it needs information from another tool—or wants to push data into one—Chatlane sends an HTTP request to a URL you provide, hands the response back to the agent, and the agent uses it in the conversation. No middleware, no scheduled jobs, no code in Chatlane.

This guide walks through setting up a webhook action end-to-end and shows three real examples: looking up a customer in your CRM, fetching live pricing, and creating a Shopify order.

If you're new to agent actions in general, start with Let your AI agent take action for the broader picture (in-Chatlane actions, Zapier, audit trail). This article focuses on the webhook type specifically.

When to use a webhook

Use a webhook when the data lives in—or needs to land in—your own systems:

  • Reading data: pull a customer's plan, order history, or shipping status into the conversation so the agent can answer accurately.
  • Writing data: create a ticket, update a CRM record, post a draft order, or notify an internal tool.

If the action you need is already a Chatlane built-in (close conversation, add a tag, update contact details) use that instead. If you'd rather not manage URLs and auth, and the destination is one of the thousands of apps Zapier covers, use Zapier. Webhooks are the right pick when you want a direct, low-latency call to your own API.

How webhook actions work

The flow is simple:

  1. The agent decides it needs the action, based on what's happening in the conversation and the action's description.
  2. The agent fills in any parameters you've defined (e.g. an order ID, a product SKU).
  3. Chatlane builds the request, substituting placeholders like {{contact.email}} and {{parameters.sku}} into the URL, headers, and body.
  4. Chatlane calls your API with the chosen HTTP method.
  5. The response is returned to the agent—status code, JSON body, and timing—so it can read fields and reply naturally to the customer.

Every call is recorded in agent logs so you can audit what was sent, what came back, and how long it took.

Setting up a webhook action

In the agent's settings, add a new action and choose Webhook. You'll be asked for the following.

Name and description

The description is the most important field on the whole form. It's how the agent decides when to use this action. Write it like an instruction to a new teammate—specifically, when this action should be called and what it returns.

Good description: "Look up the customer's current plan, renewal date, and account owner from the CRM. Use this whenever the customer asks anything about their plan, billing, or account."

Vague description: "CRM lookup."

HTTP method

Pick one of GET, POST, PUT, PATCH, DELETE. Use GET to read, POST to create, PUT/PATCH to update, DELETE to remove.

Webhook URL

The endpoint to call. Placeholders like {{contact.email}} or {{parameters.order_id}} are substituted before the request is sent, so you can build dynamic URLs:

https://api.acme-crm.com/v1/customers?email={{contact.email}}

Headers

Key/value pairs sent with every request. This is where authentication goes—Bearer tokens, API keys, custom headers. Placeholders work here too.

Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json

Query parameters

For GET and DELETE requests. Same placeholder rules apply. (You can also put query parameters straight into the URL if you prefer.)

Request body

For POST, PUT, and PATCH. A JSON template—anywhere you want a dynamic value, drop a placeholder:

{
  "email": "{{contact.email}}",
  "subject": "{{conversation.subject}}",
  "sku": "{{parameters.sku}}"
}

Parameters (the schema)

Parameters are the values the agent itself fills in at call time. You define them as a small schema—name, type, description, whether they're required—and the agent reads each parameter's description to decide what to pass.

{
  "type": "object",
  "properties": {
    "order_id": {
      "type": "string",
      "description": "The customer's order number, e.g. #1042"
    },
    "reason": {
      "type": "string",
      "description": "Short reason for the refund"
    }
  },
  "required": ["order_id", "reason"]
}

The agent will pull order_id and reason from what the customer said (or ask the customer if it's not clear) and Chatlane substitutes {{parameters.order_id}} and {{parameters.reason}} into your URL/headers/body.

Available placeholders

You can use these anywhere a placeholder is supported (URL, headers, query params, body):

Contact

  • {{contact.id}}
  • {{contact.name}}
  • {{contact.email}}
  • {{contact.phone_number}}
  • {{contact.attributes.YOUR_ATTRIBUTE}} — any custom contact attribute

Conversation & inbox

  • {{conversation.id}}
  • {{conversation.subject}}
  • {{inbox.id}}
  • {{inbox.name}}

Sender (the user or agent that triggered the run)

  • {{sender.id}}
  • {{sender.name}}
  • {{sender.email}} (empty for agents)
  • {{sender.type}}user or agent

Agent-supplied parameters

  • {{parameters.YOUR_PARAM}} — anything you defined in the schema above

If a placeholder has no value (e.g. {{contact.email}} for an anonymous website visitor), it's replaced with an empty string.

Testing your webhook

Every webhook action has a Test webhook panel. It shows the resolved URL, headers, and body after placeholder substitution, lets you fill in test parameter values, and calls your endpoint live so you can see the real status code, response body, and execution time before turning the action loose on customers.

Use the test panel any time you change a URL, header, or body template. It's much faster than waiting for a real conversation to trigger it.

Example 1 — Retrieve a customer record from a CRM

Scenario: A customer messages asking "what plan am I on and when does it renew?". The agent should look the customer up by email and answer with their actual plan.

Configuration:

Name:        Look up customer in CRM
Description: Look up the customer's current plan, MRR, renewal date,
             and account owner. Use this whenever the customer asks
             about their plan, billing, or account.
HTTP Method: GET
Webhook URL: https://api.acme-crm.com/v1/customers?email={{contact.email}}

Headers:

Authorization: Bearer YOUR_CRM_API_TOKEN
Accept: application/json

Parameters: none — the contact's email comes from the conversation.

Sample response your CRM returns:

{
  "id": "cus_8421",
  "email": "[email protected]",
  "plan_name": "Growth (Annual)",
  "mrr": 199,
  "renewal_date": "2026-09-14",
  "account_owner": "Jamie Lee"
}

What the agent says: "You're on the Growth (Annual) plan, which renews on 14 September 2026. Your account owner is Jamie Lee—want me to loop them in?"

The agent picks the right fields from the JSON automatically because they're meaningfully named.

Example 2 — Get live pricing from an external API

Scenario: A prospect asks "how much for 50 seats on the annual plan?". Instead of hard-coding pricing in the agent's instructions, call your pricing service for an authoritative quote.

Configuration:

Name:        Get pricing quote
Description: Fetch a real-time pricing quote for a given SKU, quantity,
             and term. Use this whenever a prospect or customer asks
             for a price, total cost, or comparison between monthly
             and annual.
HTTP Method: POST
Webhook URL: https://pricing.example.com/quote

Headers:

Authorization: Bearer YOUR_PRICING_API_TOKEN
Content-Type: application/json

Parameters (schema):

{
  "type": "object",
  "properties": {
    "sku": {
      "type": "string",
      "description": "Product SKU. Use 'GROWTH' for Growth plan, 'SCALE' for Scale plan."
    },
    "quantity": {
      "type": "integer",
      "description": "Number of seats requested"
    },
    "term": {
      "type": "string",
      "enum": ["monthly", "annual"],
      "description": "Billing term"
    }
  },
  "required": ["sku", "quantity", "term"]
}

Request body template:

{
  "sku": "{{parameters.sku}}",
  "quantity": {{parameters.quantity}},
  "term": "{{parameters.term}}",
  "customer_email": "{{contact.email}}"
}

Sample response:

{
  "sku": "GROWTH",
  "quantity": 50,
  "term": "annual",
  "unit_price": 16,
  "total": 9600,
  "currency": "USD",
  "discount_applied": "ANNUAL_20"
}

What the agent says: "For 50 seats on the Growth annual plan, that's $16/seat/month, so $9,600/year (with the 20% annual discount applied). Want me to send a quote?"

Note the body template uses {{parameters.quantity}} without surrounding quotes because quantity is an integer. Strings should be quoted; numbers and booleans should not.

Example 3 — Create a Shopify draft order

Scenario: The customer confirms what they want and the agent creates a draft order in Shopify, then shares the invoice link so they can pay.

Configuration:

Name:        Create Shopify draft order
Description: Create a draft order in Shopify for the current customer.
             Use this only after the customer has confirmed the variant
             and quantity they want to buy. Returns an invoice URL the
             customer can use to complete checkout.
HTTP Method: POST
Webhook URL: https://your-shop.myshopify.com/admin/api/2024-10/draft_orders.json

Headers:

X-Shopify-Access-Token: shpat_YOUR_ADMIN_API_TOKEN
Content-Type: application/json

Parameters (schema):

{
  "type": "object",
  "properties": {
    "variant_id": {
      "type": "integer",
      "description": "Shopify product variant ID the customer wants to buy"
    },
    "quantity": {
      "type": "integer",
      "description": "Number of units"
    },
    "discount_code": {
      "type": "string",
      "description": "Optional discount code to apply"
    }
  },
  "required": ["variant_id", "quantity"]
}

Request body template:

{
  "draft_order": {
    "line_items": [
      {
        "variant_id": {{parameters.variant_id}},
        "quantity": {{parameters.quantity}}
      }
    ],
    "customer": {
      "email": "{{contact.email}}"
    },
    "note": "Created by Chatlane agent — conversation #{{conversation.id}}",
    "use_customer_default_address": true
  }
}

Sample response (trimmed):

{
  "draft_order": {
    "id": 1093480001,
    "name": "#D42",
    "total_price": "129.00",
    "currency": "USD",
    "invoice_url": "https://your-shop.myshopify.com/12345/invoices/abc123"
  }
}

What the agent says: "All set—I've created draft order #D42 for $129. Here's your secure checkout link: Pay invoice"

How the agent uses the response

Whatever JSON your endpoint returns is handed back to the agent as structured data. The agent reads the relevant fields and weaves them into a natural reply—you don't have to extract specific fields yourself. Clear field names (plan_name, invoice_url, total_price) work better than cryptic ones (pn, iu, tp).

If your API returns a non-2xx status code, the agent treats the call as failed. It will usually apologise to the customer and either retry, hand off to a human, or move on—depending on how you've instructed it. If you want specific error messages surfaced, return them in a message or error field in the response body.

Authentication and security

  • Auth lives in headers. Bearer tokens, API keys, and custom auth headers are all set in the Headers field. Placeholders work in headers, so you can pass things like {{contact.attributes.api_key}} if appropriate.
  • Use least-privilege tokens. A read-only token for CRM lookups; a scoped token for Shopify orders. Don't share an admin token across actions.
  • Allowlist Chatlane's egress IPs at your firewall if your endpoint is internal. Contact support for the current IP list.
  • Don't expose secrets in URLs. Tokens belong in headers, not query strings—query strings can show up in logs.
  • HMAC request signing isn't built in. If your endpoint requires it, terminate the call at a small middleware (e.g. a Lambda or Cloudflare Worker) that signs and forwards the request.

Limits and troubleshooting

  • Timeouts. Each request has a 10-second connect timeout and a 60-second total request timeout. If your API can be slow, return a "queued" response quickly and finish the work asynchronously rather than blocking on the call.
  • No automatic retries. If a call fails, it fails. Make sure your endpoint is idempotent (especially POSTs that create things) so the agent can safely retry on its own if you instruct it to.
  • Success = 2xx. Any 4xx or 5xx response is treated as a failure.
  • Common issues. 401/403 → check the Authorization header. 404 → check the URL after substitution. 422 → check the request body matches the API's schema. Use the Test webhook panel to see the resolved request before going live.
  • Audit and debug. Every call shows up in agent logs with the request, response, and timing—use this to diagnose live issues.

Next steps

Was this article helpful?
Your feedback helps us improve our docs.