laravel-notification-channels/rocket-chat
A Laravel notification channel for sending messages to Rocket.Chat. Integrate Rocket.Chat incoming webhooks with Laravel notifications to deliver alerts, updates, and custom messages from your application.
This is a small, focused channel package: it translates a Laravel notification into an incoming-webhook POST to Rocket.Chat. There is no API client, no OAuth, no bot user — just a webhook URL and a payload. That makes it a ~15 minute integration if you already run Rocket.Chat.
Requirements (as of v0.6.0): Laravel 10 or 11. If you are still on Laravel 9 or below, Composer will resolve you to ^0.5 — verify with composer show laravel-notification-channels/rocket-chat before debugging anything else, because the API surface is the same but you'll be running an unmaintained branch.
1. Install
composer require laravel-notification-channels/rocket-chat
The package auto-discovers its service provider. Publish the config if you want a single default webhook:
php artisan vendor:publish --tag=rocket-chat
2. Create the webhook in Rocket.Chat
As an admin: Administration → Integrations → New → Incoming Webhook. Enable the script if you need to override the target channel per message, and copy the generated URL (https://chat.example.com/hooks/<id>/<token>). Treat that URL as a secret — anyone with it can post to your workspace.
3. First notification
php artisan make:notification DeployFinished
use NotificationChannels\RocketChat\RocketChatChannel;
use NotificationChannels\RocketChat\RocketChatMessage;
public function via($notifiable): array
{
return [RocketChatChannel::class];
}
public function toRocketChat($notifiable): RocketChatMessage
{
return RocketChatMessage::create()
->to('#deploys')
->content("Deploy of `{$this->tag}` finished in {$this->seconds}s");
}
public function routeNotificationForRocketChat($notifiable): string
{
return config('rocket-chat.webhook');
}
Fire it with Notification::route('rocketchat', config('rocket-chat.webhook'))->notify(new DeployFinished(...)) for a quick smoke test before wiring it to a model.
Where to look first: the README's message-builder section. The whole public surface is essentially RocketChatMessage (fluent builder) plus RocketChatChannel (transport). Everything else is Rocket.Chat's own webhook payload spec, which the package proxies almost 1:1.
Per-notification webhook override (multi-workspace). The most common real-world need is "alerts go to one workspace, customer notifications to another." Override on the message rather than globally:
return RocketChatMessage::create()
->webhook(config('services.rocket_chat.alerts_webhook'))
->to('#alerts')
->content('Disk usage above 90%');
Keep the URLs in config/services.php under env vars, not in the notification classes, so staging and production don't cross-post.
Routing through the notifiable. If Rocket.Chat destinations are per-tenant or per-user, implement routeNotificationForRocketChat() on the model and store the webhook URL (or channel name) in a column. This is the cleanest way to support "each team has its own Rocket.Chat channel" without touching notification code.
On-demand notifications for one-off scripts and health checks. No model required:
Notification::route('rocketchat', $webhook)
->notify(new SystemAlert('Queue worker restarted'));
Great for scheduled commands (Kernel::schedule → notify on failure) and for a php artisan rocket-chat:test-style command you write yourself.
Reusable message factory. Because the payload is trivial, centralize formatting in one place:
class RocketChatAlert
{
public static function make(string $level, string $body): RocketChatMessage
{
return RocketChatMessage::create()
->to(config('services.rocket_chat.channel', '#alerts'))
->content($body)
->alias(match ($level) {
'error' => 'Error Bot',
'warn' => 'Warning Bot',
default => 'Info Bot',
});
}
}
Every notification then returns RocketChatAlert::make('error', $message), which keeps emoji/alias/formatting consistent and makes a future swap (add attachments, colors) a single-file change.
Queue everything. Almost every Rocket.Chat outage, DNS hiccup, or 500 from the webhook endpoint will surface as a notification exception. Implement ShouldQueue on notifications so a slow Rocket.Chat doesn't block a request, and let Laravel's retry/backoff handle transient failures. Set $tries = 3 and $backoff = [10, 60] rather than the default infinite retries. Note that Laravel 10 and 11 differ in how $backoff and retryUntil() interact with job batches — if you upgrade majors, re-check the retry behaviour of your alerting jobs specifically, since that's exactly where a silent change hurts.
Conditional channels. Combine with other channels and gate on environment:
public function via($notifiable): array
{
return app()->isProduction()
? [RocketChatChannel::class, 'mail']
: ['mail'];
}
This is the single biggest quality-of-life win: dev/test runs never spam the team channel.
Testing. Use Notification::fake() and assert against the channel, not the HTTP call:
Notification::fake();
Notification::route('rocketchat', 'https://chat.test/hooks/x/y')
->notify(new DeployFinished('v1.2.3', 42));
Notification::assertSentOnDemand(DeployFinished::class);
For an actual payload assertion, bind a fake RocketChatChannel or use Http::fake() and inspect the recorded request body — the channel posts JSON, so Http::assertSent(fn ($r) => $r['text'] === '...') works and catches formatting regressions that Notification::fake() misses.
CI matrix. If you maintain more than one app on this package, pin per-app (^0.6 for Laravel 10/11, ^0.5 for anything older) and run the notification tests against both. v0.6.0 is a compatibility bump, not a behaviour change, so the same payload assertions should pass on either — anything that doesn't is a signal your code touched framework internals.
Check which version you actually got. "Support for Laravel 10 + 11" means Composer will happily hand you 0.5.x on Laravel 9 without complaint. Run composer show laravel-notification-channels/rocket-chat and confirm 0.6.x before assuming you have the maintained code path; a surprising number of "the package is broken" reports turn out to be an old resolved version.
The config key is easy to get wrong. The channel resolves the webhook from the notifiable first, then from the message override, then from the package config. If messages silently go nowhere, check php artisan config:clear first — a cached config with a stale/empty webhook is the #1 cause of "it worked yesterday."
A 200 does not mean delivered. Rocket.Chat incoming webhooks frequently return 200 with a body like {"success": false} when the target channel doesn't exist or the integration is disabled. The package surfaces HTTP failures as exceptions but cannot interpret Rocket.Chat's application-level error. If delivery matters, assert on the response body yourself (extend the channel or wrap your own sender) rather than assuming green = delivered.
Channel override requires the integration script enabled. ->to('#some-channel') only works if the incoming webhook has the "Script Enabled" / channel-override setting turned on. Otherwise Rocket.Chat silently posts to the integration's default channel. This confuses people constantly because the message does arrive — just in the wrong place.
Rocket.Chat markdown ≠ Slack markdown. Rocket.Chat supports a subset with its own quirks (*bold*, _italic_, backticks for code, [text](url)). Slack-style *bold* vs. **bold** differences and newline handling bite when you copy message templates from a Slack channel package. Also escape user-supplied content — a stray @all in a message body will ping the whole channel.
Alias/emoji/avatar are cosmetic and often ignored. These are webhook fields, and Rocket.Chat may drop them depending on integration settings and workspace version. Don't build logic that depends on them appearing.
Message size and rate limits are Rocket.Chat's, not Laravel's. Very long stack traces will be truncated or rejected. For exceptions, send a summary plus a link to the log rather than the full trace. For high-volume events (per-request alerts), batch into a digest notification instead of one POST per event.
Retries can duplicate. Queued notifications retried after a timeout may double-post. There is no idempotency key in the Rocket.Chat webhook API, so make messages self-identifying (include an incident ID or timestamp) and avoid retrying aggressively on timeouts.
Dependency risk is lower than it was, but watch the majors. v0.6.0 tracking Laravel 10 + 11 is a good sign the package is alive; the caveat is that these releases are compatibility bumps, so the feature surface is stable and small rather than evolving. 13 stars and zero dependents still means you're the primary maintainer of your own edge cases. The package is thin enough to read in ten minutes — when something misbehaves, open RocketChatChannel.php and RocketChatMessage.php directly rather than searching for docs. If it ever lags a future Laravel major, the extension point is straightforward: keep your RocketChatMessage construction isolated behind a small factory class so you can swap the transport for a hand-rolled Http::post() without touching every notification.
Extension point for richer payloads. If you need attachments, colors, or fields the fluent builder doesn't expose, you can usually pass them through the underlying payload array or subclass the message class — the builder is a thin wrapper over Rocket.Chat's text/channel/alias/attachments JSON. Do this in a subclass, not inline, so upgrades are a diff you can review.
How can I help you explore Laravel packages today?