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

Laravel Notification Channel Instagram Laravel Package

ka4ivan/laravel-notification-channel-instagram

Send Laravel notifications through Instagram Messenger with this channel. Install via Composer, configure your Instagram API version, access token, and profile ID in services.php, then set start buttons using the provided Artisan command.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and configure credentials. The package only ships a channel and one Artisan command, so setup is config-first:

composer require ka4ivan/laravel-notification-channel-instagram

Add an instagram block to config/services.php (it lives there, not in a package-published config file):

// config/services.php
'instagram' => [
    'api_version' => env('INSTAGRAM_API_VERSION', '22.0'),
    'access_token' => env('INSTAGRAM_ACCESS_TOKEN', ''),
    'profile_id'   => env('INSTAGRAM_PROFILE_ID', ''),
    'start_buttons' => [
        ['question' => 'Start', 'payload' => 'start'],
    ],
],

Then set INSTAGRAM_API_VERSION, INSTAGRAM_ACCESS_TOKEN, INSTAGRAM_PROFILE_ID in .env per environment and run php artisan config:clear (or config:cache) — a stale cached config with an empty token is the most common “it silently does nothing” cause.

2. Find your profile ID before writing any code. The README’s one-liner is your smoke test that the token is valid and has the right scopes:

curl -X GET "https://graph.instagram.com/me?fields=id,username&access_token=ACCESS_TOKEN"

If this fails, stop — nothing in Laravel will work. Getting an Instagram professional account, a Meta app, and messaging permissions granted is the real setup cost of this package, not the composer step.

3. Register start buttons (ice breakers) once per profile. This is a one-off, per-profile API mutation, not something you do at boot:

php artisan instagram:set-start-buttons
# or override config ad hoc:
php artisan instagram:set-start-buttons --access_token=... --profile_id=... --api_version=22.0

Treat this as a deploy/ops task per environment, and re-run it whenever you edit start_buttons in config — editing config alone changes nothing on Meta’s side.

4. Store the recipient’s IGSID on your notifiable. The channel needs an Instagram-scoped user ID (IGSID), not a username or handle. Add an instagram_id column (nullable, indexed) to the model you notify and implement routing:

public function routeNotificationForInstagram()
{
    return $this->instagram_id;
}

5. First use case — a transactional “channel connected” notification. Start with something you can verify by hand before wiring it into business flows:

use Illuminate\Notifications\Notification;
use NotificationChannels\Instagram\InstagramChannel;
use NotificationChannels\Instagram\InstagramMessage;

class ChannelConnected extends Notification
{
    public function via($notifiable)
    {
        return [InstagramChannel::class];
    }

    public function toInstagram($notifiable)
    {
        return InstagramMessage::create()
            ->to($notifiable->instagram_id) // optional if routeNotificationForInstagram() exists
            ->text('Congratulations, the communication channel is connected');
    }
}

Send it synchronously the first time ($user->notify(new ChannelConnected)) so exceptions surface in your face rather than in a queue worker log. Once it works, add ShouldQueue to the notification class — not before.

Where to look first: the README’s Available Message methods and Available Button methods lists are the complete API surface. There is no separate docs site, no published config, and no facade — if a method isn’t in those two lists, it doesn’t exist.

Implementation Patterns

Model Instagram as an extra channel on existing notifications, not a new subsystem. The highest-leverage pattern is multi-channel fan-out: one notification class, an extra entry in via(), an extra toInstagram() method, and Instagram rides along with email/SMS on the same notifiable and the same queue. This makes rollout and rollback a one-line change per notification.

Gate the channel on opt-in and on a resolvable IGSID. Because the recipient must already have an IGSID and (per Meta policy) an existing conversation, make via() conditional rather than letting the API reject at send time:

public function via($notifiable)
{
    return array_filter([
        'mail',
        $notifiable->instagram_id && $notifiable->wants_instagram ? InstagramChannel::class : null,
    ]);
}

Per-message config overrides are your multi-tenant / multi-brand escape hatch. Added in 1.2.0, setApiVersion(), setAccessToken(), and setProfileId() let a single app send from different Instagram profiles without touching global config:

return InstagramMessage::create()
    ->setProfileId($tenant->instagram_profile_id)
    ->setAccessToken($tenant->instagram_token)
    ->text($this->message);

Resolve those credentials from your own store (or a secrets manager) in the notification’s constructor or toInstagram(). The catch: this puts credentials into the message object, which is serialized if the notification is queued — see Gotchas.

Use buttons to close the loop, and postbacks as your inbound hook. There is no webhook handler in this package, so the only inbound signal you get is the postback payload coming back through Meta’s webhook to your app:

use NotificationChannels\Instagram\Components\Button;

return InstagramMessage::create()
    ->to($notifiable->instagram_id)
    ->text('How can we help?')
    ->buttons([
        Button::create('View Website', $url)->isTypeWebUrl(),
        Button::create('Start Chatting', ['user' => $this->user->id])->isTypePostback(),
    ]);

Keep postback payloads small, JSON-encodable, and self-describing (include an action key and an entity id) — you’ll be parsing them yourself later.

Attachments: prefer attachMany() for galleries, attach() for singles. Both take AttachmentType::IMAGE|AUDIO|VIDEO plus public URLs. Host media on a CDN or public disk — Meta fetches the URL, so storage_path() and signed-URL-behind-auth links will fail.

return InstagramMessage::create()
    ->attach(AttachmentType::IMAGE, url('images/'.$this->product->id))
    ->to($notifiable->instagram_id);

Queue everything, and wrap it in a throttled job scope. The package has no rate-limit handling, so the standard Laravel pattern applies: ShouldQueue on the notification, and either a RateLimited middleware on a wrapping job or a Redis throttle keyed by profile ID. This matters most with attachMany() and any bulk/broadcast send, where you can trivially burst past Meta’s per-profile limits.

Compose, don’t subclass. Rather than extending InstagramMessage, build small helpers in your app layer that return a configured InstagramMessage (e.g. InstagramTemplates::orderShipped($order)). The message class is fluent and cheap to construct; the channel is a thin adapter that you should keep swappable.

Test with Notification::fake() — do not hit the Graph API in tests. Assert on the channel and on message contents rather than on HTTP:

Notification::fake();
$user->notify(new OrderShipped($order));
Notification::assertSentTo($user, OrderShipped::class, function ($n, $channels) use ($order) {
    return in_array(InstagramChannel::class, $channels);
});

If you want coverage of the payload itself, add one integration test against a real sandbox profile, gated behind an env flag, and keep it out of CI default runs.

Integrate one notification type at a time. A pragmatic rollout order: (1) account/channel-connected confirmation, (2) transactional status updates the user explicitly asked for, (3) promotional/reminder messages last — the last category is the one Meta’s policy most constrains, and the one you least want to debug alongside a new channel.

Gotchas and Tips

IGSID ≠ username. to(), routeNotificationForInstagram(), and the instagram_id column all want the Instagram-scoped user ID from a real interaction. There is no lookupByUsername() helper in this package; you get IGSIDs from webhook events or from Meta tooling. Plan how your app learns and persists them, or you’ll have a channel nobody can receive on.

The messaging window is a product constraint, not a bug. Meta restricts when a business can initiate or continue a conversation. A notification that works in testing can be silently rejected for a user who hasn’t messaged you recently. Don’t design flows that assume Instagram is a general-purpose push channel; treat it as conversation-continuation.

Credential handling in queued notifications. setAccessToken() / setProfileId() set values on a message object that gets serialized into the queue payload. Use a short-lived token or resolve the token lazily inside toInstagram() on the worker rather than passing a long-lived token through the queue/job table. Either way, ensure failed-job payloads aren’t dumped into logs or an exception tracker.

Failures are opaque. The fluent builders hide the raw Graph API payload, so a rejection surfaces as a generic API error. When you first integrate, temporarily log the underlying response (or throw with the body attached) so you can tell apart “bad token”, “outside messaging window”, “recipient unreachable”, and “media URL not publicly fetchable”. Then decide, per notification, whether a failure should fail the job (retryable) or be swallowed (non-critical).

No built-in retries, backoff, idempotency, or delivery receipts. Supply these yourself: retryUntil/backoff on the queued notification, a failed() hook that alerts, and an idempotency guard in your own code if you re-send on retry. The package won’t dedupe for you.

Attachments are fetched by Meta, not by you. URLs must be publicly reachable at send time. Respect the documented ceilings — image 8MB (png/jpeg/gif), audio and video 25MB (aac/m4a/wav/mp4; mp4/ogg/avi/mov/webm) — and enforce them client-side so you fail fast instead of eating an API round-trip. Don’t hand it temporary signed URLs that expire before Meta fetches them.

attachMany() semantics are undocumented. The README doesn’t state whether it produces one message with N attachments or N messages. Verify against a sandbox profile before relying on ordering, rate-limit budgeting, or idempotency assumptions for a gallery send.

Validate the three-button cap yourself. The README says “up to 3” buttons of type web_url or postback, but the fluent API won’t stop you at four or with an unset type. Use the ButtonType enum and isTypeWebUrl() / isTypePostback() helpers rather than passing raw strings, and assert the count in your own message factory.

Config is effectively single-profile by default. The services.instagram block is global. Multi-brand/multi-tenant setups must thread per-message overrides through every notification, and own credential resolution entirely — this is application logic, not package logic.

start_buttons in config does nothing until you run the command. It’s a one-way push to Meta. Re-run php artisan instagram:set-start-buttons after every edit, in every environment, and remember it’s a live mutation of the profile’s ice breakers, not a local setting.

Maintenance risk is real and concentrated. With ~5 stars, zero dependents, and a single maintainer, treat this as a vendored dependency: pin a version, keep the payloads you rely on under your own tests, and be prepared to fork or replace the channel class if the Graph API shifts. Because it’s a thin adapter over Laravel notifications, replacement cost is low — which is also why you should keep Instagram-specific logic out of your domain code and behind a small template/factory layer.

Verify framework and HTTP-client compatibility explicitly. The changelog shows Laravel 9 through 13 support across releases, so confirm your Laravel/PHP pair is covered by the version you install, and that Guzzle/HTTP client versions align with the rest of the app. Don’t assume from the badge.

Debugging checklist, in order: token valid (/me curl) → instagram_id present and non-null → config not cached stale → notification actually queued and the worker running → media URL publicly fetchable → within messaging window → button count/type valid → then look at the logged API response body.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package