Look up Intercom contacts from a WhatsApp chatbot by email or phone number, fetch account details and custom attributes, and reply with live context. When a customer messages you on WhatsApp, your bot already knows who they are.
Before following this guide, read the External API Request step foundation guide. It covers every field in the step interface so this guide can focus on Intercom-specific values.
Unlike most APIs in this series where you search via GET query parameters, Intercom's contact search endpoint is a POST request with a structured JSON body. Your External API Request step must be set to POST, not GET, even though you are only reading data. This is the most common mistake when first setting up this integration.
Intercom uses a workspace-level access token. One token gives access to all contacts, conversations, and data in your workspace.
The Intercom access token gives full read and write access to your workspace. Store it securely in WA.Expert's step configuration and do not share it. If compromised, regenerate it from the same Developer Hub page.
Intercom REST API reference: developers.intercom.com/docs
Intercom contact search is a POST with a JSON body. In your WA.Expert chatbot flow, collect the customer's email as {{customer_email}}, then add an External API Request step:
| Field | Value | Notes |
|---|---|---|
| Select Method | POST | The contacts search endpoint requires POST even for read-only queries. |
| Request URL | https://api.intercom.io/contacts/search | Single fixed URL for all contact searches. No query parameters. |
| Authorization | Bearer YOUR_INTERCOM_ACCESS_TOKEN | Standard Bearer auth. Paste the token from Step 1 after 'Bearer '. |
| Content-Type | application/json | Required for POST requests with a JSON body. |
| Intercom-Version | 2.11 | Required on every Intercom API request. Specifies the API version. Use 2.11 for stable behaviour. |
| Body | JSON search query (see below) | Structured filter query. Field, operator, and value define what to search. |
| Choose Response Type | JSON | Intercom returns structured JSON. |
Without Intercom-Version: 2.11, Intercom defaults to the version your Developer Hub app was originally registered on, which may be older and have different field names. Always include this header.
The JSON body of the search request defines what to look for. Every search follows the same structure.
| What you want | JSON body |
|---|---|
| Contact by email | {{"query": {{"field": "email", "operator": "=", "value": "priya@example.com"}}}} |
| Contact by phone | {{"query": {{"field": "phone", "operator": "=", "value": "+919820000001"}}}} |
| Contact by name | {{"query": {{"field": "name", "operator": "=", "value": "Priya Sharma"}}}} |
| Users only (not leads) | {{"query": {{"operator": "AND", "value": [{{"field": "email", "operator": "=", "value": "priya@example.com"}}, {{"field": "role", "operator": "=", "value": "user"}}]}}}} |
Intercom supports AND and OR operators at the top level of the query value array for multi-field searches.
A successful Intercom contact search response looks like this:
{
"type": "list",
"data": [
{
"type": "contact",
"id": "64a1b2c3d4e5f6a7b8c9d0e1",
"workspace_id": "abc123",
"external_id": "usr_001",
"role": "user",
"email": "priya@example.com",
"name": "Priya Sharma",
"phone": "+919820000001",
"avatar": null,
"created_at": 1718870400,
"updated_at": 1719216000,
"last_seen_at": 1719216000,
"unsubscribed_from_emails": false,
"custom_attributes": {
"plan": "pro",
"account_value": "125000"
}
}
],
"total_count": 1,
"pages": {
"type": "pages",
"page": 1,
"per_page": 1,
"total_pages": 1
}
}
Map: data[0].id, data[0].name, data[0].email, data[0].role
data[0].custom_attributes.plan (if you store plan in Intercom)Intercom wraps search results in a 'list' object with a 'data' array. Map as data[0].name, data[0].email. If total_count is 0 or data is empty, the contact was not found. The role field tells you whether this is a 'user' (known customer) or 'lead' (anonymous visitor).
Map these response paths to variables in the External API Request step:
| Variable name | Response path | Example value |
|---|---|---|
| contact_id | data[0].id | 64a1b2c3d4e5f6a7b8c9d0e1 |
| contact_name | data[0].name | Priya Sharma |
| contact_email | data[0].email | priya@example.com |
| contact_role | data[0].role | user |
| contact_plan | data[0].custom_attributes.plan | pro |
| total_count | total_count | 1 |
custom_attributes contains any fields you have added to your Intercom contacts: plan, account value, company, region, or any other data you track. The key names match what you set in Intercom.
If the customer messaged you first within the last 24 hours, your reply is a free service conversation. For proactive outbound messages using Intercom data, use an approved Utility or Marketing template.
Once you have the contact's Intercom ID, a second GET step fetches their conversation history:
GET https://api.intercom.io/contacts/64a1b2c3d4e5f6a7b8c9d0e1/conversations
Authorization: Bearer YOUR_INTERCOM_ACCESS_TOKEN
Intercom-Version: 2.11
Response (abbreviated):
{
"type": "list",
"conversations": [
{
"id": "483",
"title": "Need help with my order",
"state": "open",
"created_at": 1719000000,
"updated_at": 1719200000
}
],
"total_count": 2
}
Map: conversations[0].id, conversations[0].title, conversations[0].state
state can be: open, closed, snoozedCustomer messages: "Can someone look into my account?"
Bot asks: "Please share the email on your account."
Customer: priya@example.com
Stored as {{customer_email}}.
External API Request step (POST):
URL: https://api.intercom.io/contacts/search
Headers:
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json
Intercom-Version: 2.11
Body:
{{"query": {{"field": "email", "operator": "=", "value": "{{customer_email}}"}}}}
Response mapped:
contact_id = "64a1b2c3d4e5f6a7b8c9d0e1"
contact_name = "Priya Sharma"
contact_plan = "pro"
total_count = "1"
Conditions: if total_count = 0 → "No account found for this email."
Bot replies (if found):
"Hi Priya Sharma, I can see your account.
Plan: Pro
Reply SUPPORT and a team member will pick this up,
or ORDERS to check your recent order status."| Symptom | Likely cause | Fix |
|---|---|---|
| 401 Unauthorized | Invalid or missing access token | Check the token from Developer Hub > Authentication tab. Ensure 'Bearer ' prefix is in the Authorization header. |
| Missing Intercom-Version error | Intercom-Version header absent | Add Intercom-Version: 2.11 as a header on every request. |
| Empty data array (total_count: 0) | Contact not found or wrong field | Try phone field if email returns nothing. Confirm the format matches what is stored in Intercom (with/without country code). |
| 405 Method Not Allowed | Using GET instead of POST | The contacts search endpoint requires POST. Change the method in your step. |
| contact_plan undefined | custom_attributes.plan not set for this contact | Check the field name in Intercom. Custom attribute keys are case-sensitive and must match exactly what you defined in Intercom. |
| rate limit 429 | Intercom plan rate limit exceeded | Intercom rate limits are plan-based. For a WhatsApp bot doing single lookups, standard limits are very generous. |
Zendesk Support API: OAuth 2.0, search tickets by customer email.
Read guide →Freshdesk API: Basic Auth with API key, ticket lookup and creation.
Read guide →Free trial, no credit card required. And if you ever get stuck, we are the only platform in India that answers you live on WhatsApp.