> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect Intercom: inbound threads in the Orbit inbox, writeback to Intercom

> Operator walkthrough for the Intercom integration: connect via OAuth (generic or direct path), automatic topic subscription after install, the signature-verified inbound event stream, posting Orbit replies and notes back into Intercom conversations, and how to recover when tenant setup stalls.

# Connect Intercom end to end

The Intercom integration is a bidirectional conversation loop:

* **Inbound** — contact, conversation, and ticket events from your Intercom workspace arrive on a signature-verified webhook receiver. Contact events upsert into your Orbit contact book, and conversation events are resolved to a live contact so inbound threads (and the campaigns that trigger off them) have full Orbit context.
* **Outbound** — Orbit posts replies, internal notes, and open/close actions back onto the Intercom conversation an Orbit contact is attached to, so an agent working the Orbit inbox keeps the customer's Intercom thread in sync.

This guide is the numbered checklist operators run when they wire a workspace up. The endpoint map it links back to is [`/api-reference/integrations`](/api-reference/integrations) — this page is the "do this, then this" walkthrough for the same surface.

## What you'll set up

1. An OAuth connection from Orbit to your Intercom workspace — via the generic connect route or the dedicated Intercom OAuth route (both end at the same connection).
2. Automatic topic registration — on a successful connect, Orbit programs webhook subscriptions on your workspace for the contact, conversation, and ticket event set.
3. The inbound event stream — HMAC-verified events, deduplicated per notification, resolved to your tenant and your contacts.
4. The outreach path — replies, internal notes, and open/close actions posted from Orbit back onto the Intercom conversation.
5. Failure-mode recovery — what non-blocking tenant-setup failure means and how to re-register.

***

## Step 1 — Connect via OAuth (admin only)

There are two connect paths. Pick **one** — they converge on the same connection object and the same post-connect setup.

### Option A — the generic connect route (recommended for most teams)

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/integrations/connect \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "integration_id": "intercom" }'
```

**Owner or admin role required**, plus the `integrations:write` scope on the API key — this is the only step that touches provider credentials. The response returns an `auth_url`; redirect the browser there and complete the Intercom consent screen.

### Option B — the dedicated Intercom OAuth route

Use this when you want a branded "Install on Intercom" button or you don't want to go through the generic connect flow:

```bash theme={null}
curl -s "https://api.orbit.devotel.io/api/v1/integrations/intercom/oauth/start?state=<your_org_id>" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

The `/oauth/start` endpoint mints a signed, short-lived `state` token (10-minute TTL) — the raw organization id is never forwarded to Intercom — and returns the Intercom `auth_url`. Redirect the operator to that URL. Intercom sends the browser back to `GET /api/v1/integrations/intercom/oauth/callback`, a public route: browsers cannot carry the session cookie reliably across Intercom's top-level redirect (especially Safari with tracking protection), so the callback is mounted before auth and the signed state round-trip is the authentication. The callback verifies the signature and TTL, exchanges the code for a workspace token, reads the workspace id, and runs post-connect setup.

A declined consent screen returns `400 INTERCOM_OAUTH_ERROR` with the provider's reason; a tampered or expired state returns `400 INVALID_OAUTH_STATE` / `400 OAUTH_STATE_EXPIRED`.

<Check>
  After OAuth consent, check `GET /api/v1/integrations` — the Intercom row should report a connected state. The Step section below confirms topic registration.
</Check>

***

## Step 2 — What Orbit configures after a successful connect

Either connect path ends in the same automatic tenant setup:

1. Orbit resolves the workspace id (`app_id`) and access token from the OAuth callback (or, on the generic path, from the integration server connection record).
2. The workspace access token, the webhook signing secret, and the confirmed topic set are persisted on the tenant's Intercom connection record. Credentials are encrypted at rest.
3. Orbit registers the full topic set on your workspace as one active subscription pointing at the Orbit receiver URL — before registering, it lists your existing subscriptions and skips topics already covered, so re-connecting never creates duplicate subscriptions.
4. Re-registration is idempotent — running connect again refreshes the stored token and re-confirms topics in place.

The registered topic set covers the full contact, conversation, and ticket lifecycle:

| Group         | Topics                                                                                                                                                                                                                                                                    |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Contacts      | `contact.created`, `contact.signed_up`, `contact.updated`, `contact.deleted`, `user.created`, `user.unsubscribed`                                                                                                                                                         |
| Conversations | `conversation.user.created`, `conversation.user.replied`, `conversation.admin.replied`, `conversation.admin.noted`, `conversation.admin.assigned`, `conversation.admin.closed`, `conversation.admin.snoozed`, `conversation.admin.unsnoozed`, `conversation.admin.opened` |
| Tickets       | `ticket.created`, `ticket.updated`, `ticket.state.updated`, `ticket.resolved`                                                                                                                                                                                             |

<Info>
  Connection stays healthy even if some topics fail to register — events for the missing topics just won't flow until you re-register (Step 5). The connection record keeps the confirmed topic list so the integration status surface can report drift.
</Info>

***

## Step 3 — The receive path: inbound events into Orbit

Intercom delivers each event as a single POST to `https://api.orbit.devotel.io/api/v1/integrations/webhooks/intercom`. The receiver:

1. **Verifies the sender.** Intercom signs payloads with `X-Hub-Signature: sha1=<hex>`, an HMAC-SHA1 of the raw request body keyed by the OAuth app's client secret. Orbit compares hex digests in constant time and fails closed — when the client secret is unset the receiver is disabled (503) rather than accepting unverified events; a bad signature is rejected. Reconnecting rotates nothing you need to redo manually.
2. **Resolves the tenant.** The event's `app_id` (your workspace id) is looked up against the connection records written at connect time so the event lands in the right tenant. Events for a workspace no tenant owns are rejected.
3. **Deduplicates.** Intercom retries a notification up to 30 times over roughly 8 hours until it sees a 2xx. Orbit stores one row per `(topic, notification id)` and no-ops re-deliveries, so retries never duplicate contact updates.
4. **Dispatches by topic:**
   * Contact events upsert the Orbit contact (email, phone, name, linked on the Intercom contact id); `contact.deleted` soft-stamps the linked Orbit contact.
   * Conversation events are resolved to the linked Orbit contact and mirrored with their conversation and assigning-admin ids, so an inbound Intercom thread carries full context in Orbit and can drive inbox views and campaign triggers.
   * Ticket events are persisted for the campaign engine to build on.

Acknowledge budget: Intercom expects a 2xx within 5 seconds, and retries on anything else. Events are persisted and dispatched inside that budget.

***

## Step 4 — The outreach path: writeback into Intercom

When an agent or the AI layer answers an Intercom-linked contact from Orbit, Orbit posts back onto the Intercom conversation:

* **Reply resolution** — Orbit finds the most recent Intercom conversation attached to the contact and, when the conversation names an assignee, posts as that admin. The admin who installed the app is the fallback author.
* **Public reply vs internal note** — a public reply goes on the customer's thread; an internal note (`note`) is admin-only — the AI agent uses it to leave context on the thread before assigning.
* **Open and close** — an Orbit-side conversation close can post a `close` on the Intercom thread, and a reopened thread can post `open`, so both inboxes agree on state.
* **Fail-open** — a writeback failure (revoked token, Intercom outage) is logged and audited, but never blocks the primary send in Orbit. If the contact has no Intercom conversation on file, Orbit falls back to its normal outbound channel.

***

## Step 5 — Failure modes and re-registration

Post-connect setup is deliberately non-blocking: a topic-registration failure never makes a connect look like it failed — the connection is live, and unregistered topics are recoverable.

| Symptom                                                       | Cause                                                                                                                          | Fix                                                                                                                                                                                                          |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Connected, but no events arrive                               | Topic registration failed at connect (`Intercom tenant setup failed (non-blocking)` in the platform log)                       | Re-run the connect flow — registration is idempotent and re-confirms topics. A platform admin can also re-register the topic set from the Intercom app management console pointed at the Orbit receiver URL. |
| `503` on OAuth start/callback                                 | Intercom OAuth not configured (`DEVOTEL_INTERCOM_CLIENT_ID`, `..._CLIENT_SECRET`, or `..._OAUTH_SIGNING_KEY` unset on the API) | Platform operator sets the env values; connect then proceeds.                                                                                                                                                |
| `400 INVALID_OAUTH_STATE` / `OAUTH_STATE_EXPIRED` on callback | Direct-path state older than 10 minutes or tampered                                                                            | Restart the install from `/oauth/start`.                                                                                                                                                                     |
| `502 INTERCOM_OAUTH_FAILED` on callback                       | Code exchange with Intercom failed                                                                                             | Retry; the provider detail stays in the platform log.                                                                                                                                                        |
| Webhooks return `401`                                         | Client secret mismatch                                                                                                         | The receiver uses the OAuth app's client secret as the signing key; reconnect or correct the app configuration.                                                                                              |
| Webhooks return `503`                                         | Signing not configured                                                                                                         | Receiver is disabled until the client secret is present.                                                                                                                                                     |
| `ticket.*` topics failed to register                          | Intercom API version header below 2.13                                                                                         | The platform pins 2.13; this indicates an override — restore the default.                                                                                                                                    |

<Note>
  Webhook deliveries that fail vanish only after Intercom exhausts its retries (\~30 attempts over \~8 hours). Because duplicates dedupe by notification id, a redelivery after recovery is safe.
</Note>

***

## Generic connect vs dedicated Intercom OAuth — which to use

|                      | Generic `POST /integrations/connect`                                                                | Dedicated `GET /integrations/intercom/oauth/start`                    |
| -------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| **Best for**         | Most teams; one uniform flow for every integration                                                  | A branded "Install on Intercom" button, or bypassing the generic flow |
| **State round-trip** | Integration-server managed                                                                          | HMAC-signed token (10-min TTL) minted per attempt                     |
| **Callback auth**    | Integration-server webhook back into Orbit                                                          | Public callback route; signed state is the auth                       |
| **After connect**    | Same setup function — workspace id + token capture, topic registration, encrypted connection record | Same setup function                                                   |

Both paths land in the same post-connect setup and produce the same connection record. Do not run both — pick one.

***

## Related reading

* [Integrations API reference](/api-reference/integrations) — endpoint map for the connect/status surface, including the dedicated Intercom OAuth route.
* [Connect HubSpot & Salesforce end to end](/guides/hubspot-salesforce-integration) — the same connect → subscribe → verify → recover pattern for CRM providers.
* [Webhook consumer guide](/guides/webhook-consumer) — how Orbit treats inbound provider webhooks generally (signature verification, retry windows, idempotency).
