How Do I Integrate With the Intercom API? (2026 Architecture Guide)
A technical architecture guide for integrating with the Intercom API — covering OAuth quirks, rate limits, cursor pagination, webhook verification, and how to skip the build entirely.
If you're here, you need to connect your SaaS product to Intercom's API and you want to know what you're actually getting into before committing engineering time. Short answer: Intercom's REST API is well-documented compared to most vendors, but it has several architectural quirks — no OAuth refresh tokens, aggressive rate limit windowing, and cursor-only pagination — that will eat more sprint cycles than you expect.
This guide breaks down the architecture required to build a production-grade Intercom integration. We will cover how to handle their specific rate limiting quirks, navigate cursor pagination, process real-time events without dropping payloads, and when it makes sense to skip the build entirely.
Why Integrating with the Intercom API Is Critical for B2B SaaS
Intercom sits at the center of customer communication for thousands of B2B companies. Conversations, contacts, companies, tickets — it's where your customers' support context lives. If your product touches customer success, sales enablement, or support analytics, an Intercom integration isn't optional. It's the thing your prospects ask about in the security questionnaire right before they ask about SOC 2.
Your buyers don't want to constantly switch tabs between your software and their customer communications platform. When your sales team moves upmarket, procurement and technical evaluators will demand native connectivity with their existing tech stack. If you fail to provide a reliable Intercom integration, enterprise buyers will simply choose a competitor who does.
Medium-complexity SaaS MVPs that require third-party integrations typically cost between $50,000 and $150,000, and integrations are consistently one of the biggest cost drivers. Engineering teams often underestimate the complexity — they assume it's a few HTTP GET requests. The reality involves maintaining stateful OAuth tokens, managing backoff queues, and parsing undocumented API errors. You need a strategy to ship this integration quickly without derailing your core product roadmap.
Understanding the Intercom API Architecture
Intercom's API is a REST API (currently on version 2.15) organized around a set of core objects that map to Intercom's product concepts.
| Object | Description | Common Operations |
|---|---|---|
| Contacts | Users and leads in the workspace | List, Search, Create, Update, Delete |
| Conversations | Support threads and messages | List, Search, Create, Reply |
| Companies | Organizations associated with contacts | List, Create, Update |
| Tickets | Structured support requests | Create, Update, Search |
| Tags | Labels for organizing contacts/conversations | Create, Attach, Detach |
| Data Attributes | Custom fields on contacts/companies | List, Create |
Authentication: The No-Refresh-Token Surprise
Intercom offers two authentication paths: Access Tokens for accessing data in your own workspace (private apps), and OAuth for building public apps that access other people's Intercom data. For B2B SaaS integrations where your customers connect their Intercom workspaces to your product, you need OAuth 2.0.
Register an Intercom Developer App to obtain a Client ID and Client Secret, then implement the authorization code flow:
sequenceDiagram
participant User
participant YourApp as Your SaaS App
participant Intercom
User->>YourApp: Clicks "Connect Intercom"
YourApp->>Intercom: Redirect to /oauth
Intercom->>User: Prompts for authorization
User->>Intercom: Approves access
Intercom->>YourApp: Redirects with authorization code
YourApp->>Intercom: POST /auth/eagle/token with code
Intercom->>YourApp: Returns Access TokenHere's the thing that trips up most teams: Intercom's OAuth implementation does not use refresh tokens. Access tokens remain valid until a user manually revokes access or the app deauthorizes itself.
This is unusual. Most OAuth providers issue short-lived access tokens with refresh tokens, and you build a whole token-lifecycle management system around that pattern. With Intercom, the token just lives forever. That sounds simpler, but it creates its own problems:
- You still need to detect revocation. If a customer revokes your app from their Intercom workspace, your stored token silently stops working. You need to handle 401 responses gracefully and notify your customer to reconnect.
- Token storage becomes a long-term security concern. A token that never expires is a token that's valuable to attackers indefinitely. Encrypt at rest, restrict access, audit usage.
- Your existing OAuth infrastructure may not fit. If you've built a generic token refresh pipeline for other integrations (Salesforce, HubSpot, etc.), Intercom is a special case that breaks that abstraction.
For more on how these OAuth edge cases compound across providers, see our guide on handling OAuth token refresh failures in production. For the broader picture of why this matters, read What is a Unified API?.
How to Handle Intercom API Rate Limits and Pagination
Rate Limits: The 10-Second Window Trap
Intercom imposes strict rate limits. Private apps have a default limit of 10,000 API calls per minute per app and 25,000 API calls per minute per workspace. That sounds generous until you read the fine print.
Although the permitted limit is measured per minute, Intercom evenly distributes it into 10-second windows. Every 10 seconds, the permitted request count resets. A default rate limit of 10,000 per minute means you can send a maximum of roughly 166 operations per 10-second period.
This windowed distribution is the detail that kills naive implementations. If you fire 500 requests in a 2-second burst — common during initial data syncs — you'll blow past the 10-second window limit instantly and start eating 429 responses.
When you hit a 429, you need to:
- Read the
X-RateLimit-Resetheader — it contains a Unix timestamp telling you when the window resets - Implement exponential backoff with jitter — not just a fixed sleep
- Track your request budget within each 10-second window proactively
Here's a minimal rate-limit-aware fetch wrapper:
async function intercomFetch(
url: string,
options: RequestInit,
retries = 3
): Promise<Response> {
const response = await fetch(url, options);
if (response.status === 429 && retries > 0) {
const resetAt = response.headers.get('X-RateLimit-Reset');
const waitMs = resetAt
? Math.max(0, Number(resetAt) * 1000 - Date.now()) + Math.random() * 1000
: 10_000; // fallback: wait for the full window
await new Promise((resolve) => setTimeout(resolve, waitMs));
return intercomFetch(url, options, retries - 1);
}
return response;
}Note the Math.random() * 1000 — that's jitter. Without it, all your worker processes will wake up at the exact same millisecond and immediately collide again.
This code is fine for a single integration. When you're managing dozens of customer workspaces hitting the same API, you need per-workspace rate tracking, request queuing, and circuit breakers. That's a distributed systems problem, not a weekend project.
Beyond the standard API limits, you must also account for concurrent connection limits. If your application attempts to open too many parallel TCP connections to Intercom's servers, you may encounter socket hangups or 502 Bad Gateway errors before you even trigger the 429 rate limit. A resilient architecture requires a centralized job queue that controls the concurrency of outbound requests across all your worker nodes.
For a deeper treatment, see our guide on handling API rate limits across multiple third-party APIs.
Cursor-Based Pagination: No Random Access
When you request a list of Contacts or Conversations, Intercom doesn't return the entire dataset at once. They use cursor-based pagination with a starting_after parameter.
Instead of passing a page=2 parameter, you extract a specific pointer from the response and include it in your next request. The response includes a pages object with the next cursor:
{
"type": "list",
"data": ["..."],
"pages": {
"type": "pages",
"next": {
"page": 2,
"starting_after": "WzE3MDc5MzAwODEwMDAsMTMxLDJd"
},
"page": 1,
"per_page": 50,
"total_pages": 10
}
}You loop until pages.next is absent. The maximum per_page value is 150. This method of pagination is designed for iterating through results sequentially — you cannot jump directly to page X. If you try to parallelize these requests, you will fail, because you cannot know the next cursor until the current request finishes.
Straightforward — but handling it across Contacts (query param starting_after), Search endpoints (nested pagination object in the POST body), and Conversations (which uses starting_after but in a slightly different response position) means your pagination code can't be fully generic without a mapping layer.
A common architectural mistake is storing cursor state in memory. If your worker crashes mid-sync, you lose the cursor and must restart from the beginning — or worse, you duplicate data. You must persist the starting_after token to a durable datastore after every successful page retrieval. This guarantees that if your process restarts, it resumes exactly where it left off.
Intercom's pagination also supports bi-directional traversal in some endpoints using ending_before. If you're building a syncing engine that fetches records created since the last sync, you must carefully manage cursor state in your database.
Read more about how different platforms handle this in How Do Unified APIs Handle Pagination Differences Across REST APIs?.
Dealing with Webhooks and Real-Time Data Sync
Polling the Intercom API every few minutes to detect changes is wasteful and will quickly exhaust your rate limits. Webhooks give you real-time push notifications when contacts are created, conversations are updated, tickets change state, and more. Configure a webhook URL in your Developer Hub to start receiving events.
Verifying the X-Hub-Signature
You cannot blindly trust incoming webhooks. Anyone who discovers your webhook URL could send fake payloads, injecting malicious data into your application.
Each webhook notification is signed by Intercom via an X-Hub-Signature header. The value is the hexadecimal representation of an HMAC-SHA1 signature computed using the JSON request body and your app's client_secret.
Raw body pitfall: You must compute the HMAC over the raw request body bytes, not a re-serialized JSON object. If your framework parses the JSON before the verification step, the re-serialized output won't match the exact byte sequence Intercom used to generate the hash. This applies to every language — Python's json.dumps adds extra spaces (use separators=(',', ':') to fix it), Node.js frameworks like Express may re-serialize, and Go's json.Marshal may reorder keys.
Here's how you verify the signature securely:
const crypto = require('crypto');
function verifyIntercomWebhook(rawBody, signature, clientSecret) {
const expectedSignature =
'sha1=' +
crypto.createHmac('sha1', clientSecret).update(rawBody).digest('hex');
// Use timingSafeEqual to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(signature)
);
}Webhook Retry Behavior: Plan for the Worst
Intercom gives you 5 seconds to respond to a webhook notification. If you don't return a response in time, the notification is treated as failed and will be retried only once after 1 minute.
One retry. That's it. Compare this to Stripe (which retries over 3 days) or Shopify (19 retries over 48 hours). If your webhook endpoint is down for 2 minutes, those events are gone. This means:
- Acknowledge immediately. Accept the payload, return 200, and process asynchronously via a queue.
- Build your own replay mechanism. Log every raw payload. If processing fails downstream, you can replay from your own store.
- Consider a webhook proxy. A lightweight intermediary that durably stores events before forwarding to your application.
sequenceDiagram
participant Intercom
participant YourEndpoint as Your Webhook<br>Endpoint
participant Queue as Message Queue
participant Worker as Worker Process
Intercom->>YourEndpoint: POST /webhook (X-Hub-Signature)
YourEndpoint->>YourEndpoint: Verify HMAC-SHA1
YourEndpoint->>Queue: Enqueue raw payload
YourEndpoint-->>Intercom: 200 OK (< 5 seconds)
Queue->>Worker: Dequeue and process
Worker->>Worker: Transform, enrich, storeBecause Intercom's webhooks only guarantee "at least once" delivery, your system might receive the exact same event twice. You must implement idempotency keys based on the event ID to prevent duplicate records. And because webhook payloads often contain partial data and vary wildly by event type (a contact.created event has a completely different schema from conversation.reply.created), your worker may need to route payloads to specific handlers and make subsequent GET requests to Intercom to fetch the full object context. For more on building resilient event ingestion, see our guide on how mid-market SaaS teams handle API rate limits and webhooks at scale.
The Hidden Costs of Building an Intercom Integration In-House
Let's be honest about what a production-grade Intercom integration actually requires:
| Component | Estimated Engineering Effort |
|---|---|
| OAuth flow + token storage + revocation detection | 1–2 weeks |
| Rate limit handling with per-workspace tracking | 1 week |
| Cursor pagination across all resource types | 1 week |
| Webhook ingestion + HMAC verification + replay | 1–2 weeks |
| Data mapping to your internal schema | 1–2 weeks |
| Error handling, logging, monitoring | 1 week |
| Total | 6–10 weeks |
That's one senior engineer fully dedicated. At a loaded cost of $80–100/hour, you're looking at $50K–$80K just for the initial build. And this is for Intercom alone. When your buyers ask for Zendesk, Freshdesk, HubSpot, and Salesforce too, the math completely breaks down.
The maintenance cost is what really compounds. Intercom ships API version updates regularly, and breaking changes — removing operations, renaming parameters, adding required fields — are released in new versions. Each version bump means regression testing, field mapping updates, and potentially rewriting pagination logic.
Worse, integrations are never truly "done." Every hour your engineers spend debugging an invalid_grant OAuth error or chasing down a changed webhook schema is an hour they're not building core product features. This technical debt compounds with every new integration you add. We analyze this exact financial trade-off in Build vs. Buy: The True Cost of Building SaaS Integrations In-House.
How to Integrate with Intercom in Minutes Using a Unified API
A unified API abstracts away provider-specific quirks behind a single, normalized interface. Instead of learning Intercom's cursor pagination, HubSpot's offset pagination, and Zendesk's cursor/page hybrid, you can normalize pagination and error handling to call one endpoint with one pagination contract.
Here's what this looks like with Truto. To list contacts from a customer's Intercom workspace:
curl https://api.truto.one/unified/ticketing/contacts \
-H "Authorization: Bearer YOUR_TRUTO_TOKEN" \
-G -d "integrated_account_id=CUSTOMER_INTERCOM_ACCOUNT"The same endpoint, same schema, same pagination format — regardless of whether the backend is Intercom, Zendesk, Freshdesk, or Jira.
What Truto Handles That You Don't Have To
Rate limit absorption. Truto tracks the 10-second window limits per workspace and automatically queues, throttles, and retries requests with appropriate backoff. Your application never sees a 429.
OAuth lifecycle management. For Intercom specifically, Truto handles the no-refresh-token quirk by monitoring token validity and notifying your app when a customer's token is revoked, so you can prompt them to reconnect. For providers that do use refresh tokens, Truto proactively refreshes them before expiry.
Cursor pagination normalization. Intercom's starting_after cursor, the nested pagination object in search endpoints, the per_page cap of 150 — all of it is abstracted into a consistent next_cursor / prev_cursor response format.
Webhook signature verification and normalization. Truto ingests Intercom webhooks, verifies the X-Hub-Signature using HMAC-SHA1 with timing-safe comparison, maps the raw event payloads (like conversation.admin.replied) into unified event types (like record:updated), and delivers them to your endpoint in a standardized schema.
flowchart LR
A[Your App] -->|Single API Call| B[Truto Unified API]
B -->|Rate-limited,<br>paginated| C[Intercom API]
B -->|Rate-limited,<br>paginated| D[Zendesk API]
B -->|Rate-limited,<br>paginated| E[Freshdesk API]
C -->|Webhooks| B
D -->|Webhooks| B
E -->|Webhooks| B
B -->|Unified Events| ATrade-offs to Consider
Using any unified API means accepting some constraints. Be clear-eyed about these:
- Schema coverage isn't 100%. Unified models cover the most common fields across providers. If you need Intercom-specific fields like
ai_agentmetadata on conversations, you may need to use the Proxy API to access raw data alongside unified calls. - You're adding a dependency. Your integration now relies on Truto's uptime and response to Intercom API changes. Evaluate the SLA, incident history, and how quickly version updates ship.
- Custom objects require configuration. If your customers use Intercom's custom objects heavily, you'll need to work with your unified API provider to map those into your schema.
The honest assessment: a unified API is the right call for 90% of B2B SaaS teams that need Intercom integration alongside other providers. The 10% who should build direct are teams with extremely specialized requirements or those where Intercom is the only integration they'll ever need.
What This Means for Your Integration Roadmap
The Intercom API is well-designed but has real operational complexity — the 10-second rate limit windows, cursor-only pagination, one-retry webhooks, and no-refresh-token OAuth all require specific engineering investment.
If Intercom is one of many integrations on your roadmap, building each one by hand creates compounding technical debt. A unified API collapses that into a single integration layer, letting your team focus on what actually differentiates your product.
Start by mapping out which Intercom objects your customers actually need (usually Contacts and Conversations), identify whether you need real-time webhook delivery or periodic sync, and evaluate whether the unified schema coverage matches your requirements. That analysis will tell you whether to build, buy, or blend.
FAQ
- Does the Intercom API use refresh tokens?
- No. Intercom's OAuth implementation does not use refresh tokens. Access tokens remain valid until the user manually revokes access or the app deauthorizes itself. You still need to handle 401 responses to detect revocation and prompt customers to reconnect.
- What is the rate limit for the Intercom API?
- Intercom allows 10,000 API calls per minute per app and 25,000 per workspace. These limits are distributed into 10-second windows, meaning you can send a maximum of roughly 166 operations per 10-second period. Exceeding this triggers HTTP 429 responses.
- How does pagination work in the Intercom API?
- Intercom uses cursor-based pagination with a starting_after parameter. The response includes a pages.next.starting_after cursor string you pass in the next request. The maximum per_page value is 150. You cannot jump to a specific page number — you must iterate sequentially.
- How do I verify Intercom webhook signatures?
- Intercom signs webhook payloads using HMAC-SHA1 with your app's client_secret as the key. The signature is sent in the X-Hub-Signature header prefixed with 'sha1='. Compute the HMAC over the raw request body bytes (not re-serialized JSON) and compare using a timing-safe function.
- How many times does Intercom retry failed webhooks?
- Intercom gives you 5 seconds to respond and retries a failed webhook only once after 1 minute. If both attempts fail, the event is lost. You should acknowledge webhooks immediately, process them asynchronously, and maintain your own replay mechanism.