xigen.io/slack-bundle
A PHP package that simplifies Slack integration for your application. Send messages, notifications, and alerts to Slack channels using webhooks or API credentials, with straightforward configuration and a lightweight setup that gets Slack connectivity working quickly.
Read this first: xigen.io/slack-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no config/slack.php, and no package discovery. In a Laravel app you cannot composer require it and expect it to boot. The realistic day-to-day leverage is one of three things: (1) you run a small Symfony service that owns Slack delivery, (2) you read its source and port the payload/config logic into a Laravel service class, or (3) you use it inside an actual Symfony app and call that app over HTTP from Laravel.
First steps (Symfony side):
composer require xigen.io/slack-bundle
Register the bundle — in modern Symfony (config/bundles.php):
return [
// ...
Xigen\SlackBundle\XigenSlackBundle::class => ['all' => true],
];
Then publish/inspect the config. Do not guess the config key — read DependencyInjection/Configuration.php in the repo first. The bundle's root alias (typically something like xigen_slack) and the exact option names are the only thing that matters, and the README is minimal. Typical shape:
# config/packages/xigen_slack.yaml
xigen_slack:
webhook_url: '%env(SLACK_WEBHOOK_URL)%'
channel: '#deploys'
username: 'Symfony Bot'
Where to look first, in order:
XigenSlackBundle.php — confirms bundle class name and namespace.DependencyInjection/Configuration.php — the real config schema (this is your documentation).Resources/config/services.* — the service ID you'll inject.First use case: a console command that posts a deploy notice.
// src/Command/DeployNotifyCommand.php
#[AsCommand(name: 'app:deploy-notify')]
class DeployNotifyCommand extends Command
{
public function __construct(private SlackService $slack) { parent::__construct(); }
protected function execute(InputInterface $in, OutputInterface $out): int
{
$this->slack->send('Deploy finished: ' . $in->getArgument('sha'));
return Command::SUCCESS;
}
}
If you're Laravel-only, skip the composer step entirely and jump to the "port it" pattern below — a 60-line SlackNotifier using Http::post() to a webhook will cost you less than wiring a Symfony bundle you don't otherwise use.
Webhook vs. bot-token. Most bundles of this vintage wrap an incoming webhook (a single URL, channel baked in) rather than the Web API. That's fine for one-way notifications and terrible for chat.update / threads / reactions. Confirm which one you have before designing around it. If it's webhook-only, treat the bundle as a fire-and-forget notifier, not a Slack client.
Event-driven notification (the pattern worth copying). The durable design is: domain event → listener → notifier. In Symfony:
class OrderShippedListener
{
public function __construct(private SlackService $slack) {}
public function __invoke(OrderShipped $event): void
{
$this->slack->send(sprintf('Order %s shipped', $event->orderId));
}
}
Laravel equivalent (what you'd actually write in a Laravel codebase):
// app/Services/Slack.php
class Slack
{
public function post(string $text, ?string $channel = null, array $blocks = []): void
{
$payload = array_filter([
'text' => $text,
'channel' => $channel,
'blocks' => $blocks ?: null,
]);
Http::timeout(5)
->retry(3, 200, throw: false)
->post(config('services.slack.webhook'), $payload)
->throw();
}
}
Bind it as a singleton and fake it in tests with Http::fake().
Channel-per-environment. Never hardcode #general. Route through config (SLACK_CHANNEL env), and in local/testing point at a private #bot-spam channel so a developer's artisan migrate doesn't page anyone.
Block Kit payloads. Rich messages are just JSON — the bundle (and any wrapper) passes it through. Build blocks as PHP associative arrays and let the HTTP client serialize:
$blocks = [
['type' => 'section', 'text' => ['type' => 'mrkdwn', 'text' => "*Deploy* `{$sha}`"]],
['type' => 'context', 'elements' => [['type' => 'mrkdwn', 'text' => "env: {$env}"]]],
];
Keep the builder in its own class; don't inline block arrays in listeners or you'll be editing five files every time the format changes.
Integration tips for a mixed stack. If you go the "Symfony microservice" route, expose POST /notify on it, authenticate with a shared secret header, and have Laravel queue a job that calls it. That gives you one place that owns Slack retries and rate limiting, and keeps Slack credentials out of the Laravel app entirely.
Decoration over forking. To add logging/retry without touching vendor code, decorate the bundle's service in services.yaml:
App\Slack\LoggingSlackClient:
decorates: xigen_slack.client # use the real service id from Resources/config
arguments: ['@.inner', '@logger']
This survives composer update and is the cleanest extension point the bundle offers.
Bundle will not autoload config in Laravel. No extra.laravel block, no provider. composer require succeeds, nothing happens, and you'll waste an hour looking for a facade that doesn't exist.
Zero stars, zero dependents is a real signal. No issue triage, no version guarantees, no security response. Pin an exact commit in composer.json ("xigen.io/slack-bundle": "dev-master#<sha>") rather than a floating branch, or fork it into your VCS and treat it as first-party code. Budget for the fork.
Config key mismatch is the #1 failure. InvalidConfigurationException or a silently unused webhook_url usually means the root alias differs from what the README shows. The Configuration.php tree is authoritative. Run bin/console config:dump-reference <alias> to see the live schema.
json_encode vs. Slack's Block Kit. Slack is strict: blocks must be an array, and empty arrays/objects sometimes get rejected. If PHP serializes an empty text as "" or turns an assoc array into an object because of non-sequential keys, Slack returns invalid_blocks with no useful detail. Run array_values() on elements/blocks lists and strip nulls before sending.
Rate limits. Incoming webhooks are roughly 1 message/sec per channel. A loop over 50 orders will get you 429 with a Retry-After header, and a naive webhook client ignores it. Batch into a single message (join lines) or throttle. Check whether the bundle surfaces the response at all — many swallow non-2xx silently, so a broken webhook URL looks identical to success.
Webhook URLs are credentials. They grant post access to the channel. Keep them in env/secret manager, rotate on leak, and never log the request body or URL at debug level.
Timeouts and blocking. If the bundle uses Guzzle with defaults, a slow Slack response can block a web request for the full timeout. Set an explicit short timeout (3–5s) and push sends to a queue (Messenger in Symfony, ShouldQueue job in Laravel). Notification failure should never fail the user's transaction.
mrkdwn escaping. Slack uses its own markup. User-generated text containing &, <, > must be escaped (&, <, >) or you'll get broken formatting and, worse, accidental <!channel> mentions. Escape raw values, then add your own markup.
Testing. Don't hit Slack in CI. If you're in Symfony, replace the client service in config/services_test.yaml with a stub that records calls. In Laravel, Http::fake() and assert on Http::assertSent() — that's another argument for the port rather than the bundle.
Extension points worth knowing: service decoration (above), a compiler pass to inject per-environment channel config, and overriding the bundle's template if it hardcodes message formatting in a Twig resource under Resources/views/. If none of those cover your need, the bundle is thin enough that porting it is cheaper than fighting it.
How can I help you explore Laravel packages today?