simonbackx/slack-php-webhook
A lightweight PHP package for sending Slack messages through incoming webhooks. Compose simple text notifications or richer attachment-based messages with a clean, framework-agnostic API that works in any PHP project.
Install the package (it is not Laravel-specific, but it drops into a Laravel app cleanly):
composer require simonbackx/slack-php-webhook
Create an incoming webhook in Slack. Go to https://api.slack.com/apps → Create New App → Incoming Webhooks → activate → Add New Webhook to Workspace → pick a channel. Copy the https://hooks.slack.com/services/... URL.
Store the URL in config, not in code:
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T000/B000/xxxx
// config/services.php
'slack' => [
'webhook_url' => env('SLACK_WEBHOOK_URL'),
],
First use case — a deployment/ops notification from an artisan command is the lowest-risk way to validate the wiring:
use SimonBackx\Slack\Webhook;
use SimonBackx\Slack\Messages\Message;
$webhook = new Webhook(config('services.slack.webhook_url'));
$message = new Message("Deploy finished on " . config('app.env'));
$webhook->send($message);
Where to look first: the README for the exact constructor/send signature, then src/Webhook.php (the HTTP call and error behavior) and the message/attachment classes under src/. Those two files tell you everything about how payloads are serialized, which matters when you need to work around missing features.
Bind it once as a singleton. Do not new Webhook(...) in controllers. Register a small service provider so the URL and any default channel/username/icon come from config:
$this->app->singleton(Webhook::class, fn () => new Webhook(
config('services.slack.webhook_url')
));
Then inject Webhook wherever needed, or expose a thin SlackNotifier wrapper with domain methods (notifyDeploy(), notifyFailedJob()) so call sites are not coupled to the package's message API. That wrapper is also your escape hatch if you later migrate to Block Kit or Laravel's built-in Slack notifications.
Always send asynchronously. A Slack HTTP round-trip inside a request is a latency and availability risk. Wrap sends in a queued job:
class SendSlackMessage implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable;
public function __construct(public array $payload) {}
public function handle(Webhook $webhook): void
{
$webhook->send(new Message($this->payload['text']));
}
}
SendSlackMessage::dispatch(['text' => '...']) from controllers, listeners, and scheduled tasks.
Model it as a Laravel notification channel if you already use Notifiable. Implement toSlack($notifiable) returning a payload array/message, and a SlackChannel::send() that delegates to the package. This keeps Slack alerts alongside mail/database notifications and gives you via() routing per user or per team.
Handle multiple destinations via config, not ad hoc strings. A config/slack.php map of deploy, errors, sales → webhook URLs lets you route by severity. Each webhook is pre-bound to a channel in Slack, so treat the URL as the channel selector.
Compose structured messages with attachments. Where the package supports attachments, use them for key/value alerts (status, environment, commit SHA, link) rather than a wall of text. Keep a few color helpers (good, warning, danger) in one place. If the message builder lacks a field you need, check whether it exposes the raw payload array — otherwise serialize your own JSON and post it directly.
Test with a fake. The package performs a real HTTP call, so unit tests should either mock the transport (Guzzle mock handler if it uses Guzzle) or, more pragmatically, point SLACK_WEBHOOK_URL at a request bin in staging and assert on the captured payload. Bind a no-op implementation of your SlackNotifier in tests/TestCase.php so the suite never hits Slack.
composer show against your PHP version; if it declares an old PHP constraint, you may need a fork or --ignore-platform-reqs while you evaluate a replacement. There is no active maintenance, so budget for migration.ok; a 200 with a non-ok body, or a 4xx, is a real failure. Check the response body/status in your wrapper and log it — do not assume send() succeeded.channel field in the payload; modern ones ignore it or require explicit configuration. Do not build routing on per-message channel — use separate webhook URLs.429s with a Retry-After. Batch or debounce notifications; do not retry aggressively.toArray()/jsonSerialize() before you reach for a workaround.text around 4,000 characters and attachments/fields have their own limits. Truncate stack traces and long diffs in your wrapper.&, <, > from user-supplied strings (Slack's own escape rules) or you will get broken formatting and accidental mentions..env/secret manager, never log it, and rotate it if it leaks. It also bypasses your app's auth — do not expose it to the frontend.webhook.site or a local nc -l listener to see the exact JSON the package emits. That is faster than reading the serializer when you are chasing a formatting bug.slack-php/slack-php-block-kit or Laravel's native Slack notification channel. This package is fine for simple text/attachment alerts, but it is a maintenance liability for anything long-lived.How can I help you explore Laravel packages today?