jeffersongoncalves/laravel-intercom
A Laravel package that integrates Intercom into your application, offering configuration and convenient access to Intercom’s API for managing contacts, conversations, and messaging.
This is the initial 1.0.0 release, so treat it as a greenfield integration rather than an upgrade — there is no migration surface, but also no battle-testing yet. Pin the exact version in composer.json until you've validated the endpoints you depend on.
Minimal setup
composer require the package, then publish its config (php artisan vendor:publish --tag=intercom-config or similar — check the README for the exact tag).INTERCOM_TOKEN to .env and confirm the config file reads from it rather than hardcoding. Add the token to your deploy secrets before running config:cache.api.eu.intercom.io / api.au.intercom.io). Getting this wrong produces confusing 401s that look like a bad token.new-ing anything, so you can swap it in tests.Where to look first
intercom/intercom-php SDK or speaks raw HTTP via Laravel's Http client. This single fact determines your testing and error-handling strategy.First use case
Identify a user the moment they register or log in: contacts()->create() (or upsert) with a stable external_id (your users.id), plus email, name, and a signed-up timestamp as a custom attribute. Everything else in Intercom — messaging, segmentation, support context — depends on that identity being right, so ship this first and verify it in the Intercom UI before building on top.
Keep the API out of the request cycle. Nearly every meaningful call here is a network hop to a third party. Fire contact sync, event tracking, and tagging from queued listeners on your User model (Registered, Updated, LoggedIn) with ->afterCommit() so you never sync a user that rolled back. Wrap the job in a retry policy and a failed() handler that logs rather than throwing into the queue's dead-letter path permanently.
Wrap the package in a thin domain service. Inject the client into something like App\Services\Intercom\CustomerSync that exposes identify(User $user) and track(User $user, string $event, array $meta). Your application code talks to your service, not to the vendor package. When the package changes method signatures between minor versions — likely, at 1.0.0 — you edit one file instead of forty.
The identify/update pattern is the workhorse. Use external_id as the canonical key and treat email as a mutable attribute. Intercom resolves identity on both, so if you ever set email as the primary key and a user changes it, you'll create duplicates that are painful to merge.
Events drive lifecycle messaging. events()->create() with a name and metadata is how you trigger onboarding sequences and re-engagement without shipping new code. Define a small enum of event names in your codebase and reject free-form strings at the call site — otherwise marketing will invent names your dashboard can't aggregate.
Tags are your segmentation primitive. Attach/detach on state transitions (plan tier, trial status, beta flag). Prefer a fixed tag vocabulary over dynamic strings like plan-{id}; Intercom's tag list becomes unusable fast.
B2B: companies before contacts. Create the company, then attach the contact with a job title and role. Do this in the same queued job as the contact sync, ordered, so a contact never appears unattached in a way support staff will misread.
Conversations and messages are admin-initiated, not user-initiated. messages()->create() outbound from an admin (admin_id required) is the typical path for in-app announcements and email follow-ups; message_type matters (inapp vs email) and the two behave very differently in the UI. Reading conversations is a support-dashboard concern — paginate with the cursor and cache aggressively, because the list endpoints are the slowest thing in the API.
Articles and help center are useful for building a self-serve "was this helpful" surface inside your app, but they're read-mostly and low-risk — a good place to start if you want a low-consequence first integration.
Testing pattern: bind a fake implementation of your own IntercomClient interface in the container for feature tests. If the package uses Laravel's HTTP client under the hood, Http::fake() gives you end-to-end coverage cheaply — assert on the request body for external_id and attributes rather than on the package's return objects.
Intercom-Version header, and behaviour changes between versions (conversations especially). If the package hardcodes a version, note it; if it's configurable, set it explicitly and don't rely on the default drifting under you.INTERCOM_TOKEN in local .env will happily create real contacts and fire real customer emails. Use a separate dev workspace, and consider a hard guard that throws in non-production unless an explicit opt-in env var is set.X-RateLimit-Remaining and back off on 429 with jitter rather than fixed sleeps. A backfill script over 50k users will hit this — chunk it and make it resumable.contacts()->search(). Never use search to verify a write; fetch by id or external_id instead. This bites people writing "did the sync work?" assertions.{type: "error.list", errors: [{code, message}]}. Map code values (not_found, parameter_invalid, unauthorized) rather than string-matching messages — messages change.config:cache will freeze your token. Almost every "works locally, 401s in staging" report for packages like this is a cached config from a build step that ran before the env var existed.external_id does not resurrect their history.1.x minor versions as potentially breaking until the package demonstrates otherwise.How can I help you explore Laravel packages today?