Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Intercom Laravel Package

darkin1/intercom

A Laravel package that wraps the Intercom API, making it easy to sync users and companies, track events, tag contacts, and manage conversations from your app. Includes configurable credentials, a simple facade, and auto-discovered service provider for quick setup.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and wire it up

composer require darkin1/intercom

Laravel 5.5+ auto-discovers the service provider. On older 5.x, register Darkin1\Intercom\IntercomServiceProvider in config/app.php and add the facade alias (Intercom → Darkin1\Intercom\Facades\Intercom).

2. Publish and configure

php artisan vendor:publish --provider="Darkin1\Intercom\IntercomServiceProvider"

Then set the env vars the config file reads — almost always a private app access token (Intercom → Settings → Developers → Private apps), not the App ID and not an OAuth token:

INTERCOM_TOKEN=xxxxxxxxxxxxxxxxxxxx
INTERCOM_SECRET=your_identity_verification_secret

3. Where to look first

  • config/intercom.php — the exact env keys and any base-URI/admin options it exposes.
  • src/IntercomServiceProvider.php — what it binds into the container and under what key (typically intercom), plus whether it binds the raw Intercom\IntercomClient or a subclass.
  • src/Facades/Intercom.php — getFacadeAccessor() tells you the container key directly.
  • The README, in full. At 15 stars and a 2021 release, the README is the spec — there is no community to fall back on.

4. First use case: identify the logged-in user

// php artisan tinker
$intercom = app('intercom'); // confirm the key from the facade accessor

$intercom->users->create([
    'user_id' => (string) auth()->id(),
    'email'   => auth()->user()->email,
    'name'    => auth()->user()->name,
]);

users.create is an upsert, so this same call handles both "first login" and "profile changed." That single call is 80% of what most apps need this package for.

Implementation Patterns

Prefer constructor injection over the facade

The facade is convenient in controllers, but for anything you'll unit test, inject the client:

use Intercom\IntercomClient;

class IntercomSync
{
    public function __construct(private IntercomClient $intercom) {}

    public function upsert(User $user): void
    {
        $this->intercom->users->create([
            'user_id'           => (string) $user->id,
            'email'             => $user->email,
            'name'              => $user->name,
            'signed_up_at'      => $user->created_at->timestamp, // seconds, not ms
            'custom_attributes' => [
                'plan'         => $user->plan,
                'company'      => optional($user->company)->name,
            ],
        ]);
    }
}

If the provider only binds the facade key and not the class, add one line to your own AppServiceProvider:

$this->app->bind(\Intercom\IntercomClient::class, fn ($app) => $app->make('intercom'));

Wire it to lifecycle events, not controllers

The cleanest integration is an observer → queued job. Never call Intercom synchronously inside a request; their p99 latency is not your p99.

class UserObserver
{
    public function saved(User $user): void
    {
        if ($user->wasChanged(['email', 'name', 'plan'])) {
            SyncUserToIntercom::dispatch($user)->onQueue('intercom');
        }
    }

    public function deleted(User $user): void
    {
        ArchiveUserInIntercom::dispatch($user->id)->onQueue('intercom');
    }
}

Use saved + wasChanged(), not updated, so first-creation is covered too — and so a no-op save doesn't burn a rate-limit token.

Track events for anything you'd otherwise poll for

$intercom->events->create([
    'event_name' => 'order-placed',
    'created_at' => now()->timestamp,
    'email'      => $user->email,
    'metadata'   => ['order_id' => $order->id, 'total' => $order->total_cents],
]);

Events drive Intercom's Series/targeting. This is the highest-leverage call after user upsert.

Identity verification for the Messenger widget

If the JS snippet uses user_hash, it must be HMAC-SHA256 of the user_id keyed with the same secret Intercom shows in settings:

$userHash = hash_hmac('sha256', (string) $user->id, config('intercom.secret'));

Pass $userHash to the Blade view. Mismatch here fails silently in the browser — it does not throw in PHP — so add an assertion in a feature test.

Admin-initiated messages

$intercom->messages->create([
    'message_type' => 'email',
    'subject'      => 'Your invoice is ready',
    'body'         => '<p>...</p>',
    'template'     => 'plain',
    'from'         => ['type' => 'admin', 'id' => config('intercom.admin_id')],
    'to'           => ['type' => 'user', 'id' => (string) $user->id],
]);

from.id must be a real admin ID — fetch it once via $intercom->admins->list() and cache it in config. Using an email here is a common 422.

Multi-workspace / multi-tenant

The singleton binds one token. For per-tenant Intercom workspaces, ignore the singleton and build the client yourself:

$client = new \Intercom\IntercomClient($tenant->intercom_token, ['base_uri' => $tenant->intercom_base_uri]);

This is also the escape hatch if you need a custom Guzzle handler for logging or retries.

Testing

Two viable approaches — pick one per test suite:

// Facade mock, for controller/feature tests
Intercom::shouldReceive('users->create')->once()->with(Mockery::on(fn ($p) => $p['user_id'] === '1'));

// Or bind a fake client into the container
$this->app->instance(\Intercom\IntercomClient::class, $fake);

Http::fake() does not work — the SDK talks Guzzle directly, outside Laravel's HTTP layer.

Neutralize it locally and in CI

// AppServiceProvider::register()
if ($this->app->environment(['local', 'testing'])) {
    $this->app->bind('intercom', fn () => new NullIntercomClient());
}

Otherwise every php artisan migrate --seed and every CI run pollutes your real Intercom workspace with test users.

Gotchas and Tips

The maintenance risk is the headline gotcha. Last release May 2021, Laravel 7 ceiling. It will likely install on 8/9/10, but you're relying on transitive intercom/intercom-php/Guzzle versions to be compatible with your PHP. Check composer why intercom/intercom-php after install, and pin the SDK version explicitly in your own composer.json. Budget a migration: the wrapper is thin, so replacing it with ~20 lines binding IntercomClient in a service provider is usually a one-afternoon job — and removes a single-maintainer dependency with 15 stars.

Data region. EU-hosted Intercom workspaces require https://api.eu.intercom.io. If the package config doesn't expose a base URI, you must construct the client manually. Symptom: 404s or "app not found" on otherwise-valid calls.

Rate limits are the most likely production incident. Intercom enforces global and per-endpoint token buckets and returns 429 with Retry-After. Bulked-up user syncs (a backfill, a CSV import) will trip it. Always route through a rate-limited queue: ->onQueue('intercom')->middleware(RateLimited::perMinute(80)), and give the job $tries = 5 with backoff() so 429s drain rather than fail.

users.create is an upsert with dedupe rules that will surprise you. Intercom dedupes on both user_id and email. If a user changes email and you pass the new one, you can create a second lead instead of updating; if you pass only an email that already exists on a different user_id, you get a conflict. Always send a stable user_id, and treat email as a mutable attribute.

Search is exact-match, not full-text. users.search() / companies.search() with a query field require precise operators (field:value, AND/OR). For enumerating large sets, use the scroll API — pagination via page breaks down past a few thousand records.

Exception surface is Guzzle. Expect GuzzleHttp\Exception\ClientException for 4xx and ServerException for 5xx. The useful detail is in $e->getResponse()->getBody() as JSON — Intercom puts a human-readable errors[].message there that the generic exception message omits. Wrap the sync job's handle() in a try/catch that logs the decoded body, or you'll debug blind.

config:cache bites during deploys. The package reads config('intercom.*') at container-resolution time. If you cache config before setting INTERCOM_TOKEN (or you're using a runtime-injected secret), the client boots with a null token and you get 401s. Also restart queue workers after any env change.

Facade alias collisions. Intercom as a root alias can clash with other SDKs or with a model/service named Intercom. If you hit "class not found" on the alias, register it under a distinct key in config/app.php.

Timestamps are Unix seconds. The PHP SDK is seconds; the JS SDK and some Intercom docs are milliseconds. Passing a millisecond value silently sets a year-57000 signed_up_at. ->timestamp on a Carbon instance is correct; ->getTimestampMs() is not.

users.delete is hard delete. For churn, use archive (users.archive($id)) — permanent deletion is irreversible and breaks event history.

Extension point worth knowing: because the provider binds a plain IntercomClient, you can rebind it in your own service provider with a subclass or a pre-configured instance carrying a custom Guzzle client (retry middleware, request logging, EU base URI) without forking the package. That's the cleanest path to modernizing this dependency incrementally — upgrade the SDK underneath while keeping the package's façade and config ergonomics in place.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons