lisennk/laravel-slack-events-api
A Laravel package for the Slack Events API that verifies incoming webhook requests and routes Slack events to your own handlers, making it easy to respond to messages, mentions, and other workspace activity.
Install the package and publish its config and migration:
composer require lisennk/laravel-slack-events-api
php artisan vendor:publish --provider="Lisennk\LaravelSlackEvents\SlackEventsServiceProvider"
php artisan migrate
Add the Slack credentials to .env. Newer Slack apps use a signing secret, not the legacy verification token:
SLACK_EVENTS_SIGNING_SECRET=your-signing-secret
SLACK_EVENTS_TOKEN=xoxb-...
Open config/slack-events.php first. It controls the webhook route, middleware, and handler registration. Then look at the published migration and the SlackEvent model — that is where incoming payloads land.
In the Slack app dashboard, enable Event Subscriptions and set the Request URL to your route, typically:
https://your-app.example.com/slack/events
Slack immediately sends a url_verification challenge. The package’s default handler should answer it automatically; once verified, subscribe to a first event such as app_mention or message.channels.
Your first real use case: store the event, then acknowledge the mention by dispatching a queued job that calls Slack’s Web API. The Events API is inbound only — it does not post replies for you.
One handler per concern. The package’s pattern is to let a handler class receive the persisted SlackEvent. Keep handlers small and delegate business logic to services or jobs:
namespace App\Slack\Handlers;
use Lisennk\LaravelSlackEvents\Models\SlackEvent;
use App\Jobs\ReplyToMention;
class AppMentionHandler
{
public function handle(SlackEvent $event): void
{
$payload = $event->data['event'];
if ($payload['user'] === config('slack.bot_user_id')) {
return; // never reply to yourself
}
ReplyToMention::dispatch($payload['channel'], $payload['text']);
}
}
Register it in config/slack-events.php using whatever handler mapping that version expects — check the published file and the vendor source for the exact key names, since the package supports several Laravel versions and the config shape has changed over time.
Acknowledge fast, work later. Slack expects a 200 within three seconds. Handlers should do almost nothing synchronously: persist or dispatch, then return. Put all HTTP calls to Slack, LLM calls, or database-heavy work behind dispatch().
Treat the SlackEvent row as your inbox. The migration generally gives you the raw payload plus identifiers such as event_id, team_id, and a handled timestamp. Use the model as an audit log and as the source for retries or replays:
SlackEvent::whereNull('handled_at')
->where('type', 'event_callback')
->each(fn ($event) => ProcessSlackEvent::dispatch($event));
Filter aggressively. Slack sends a lot you do not want: bot messages, edits, joins, channel events, and your own bot’s posts. Check event.subtype, bot_id, and the team/channel IDs before doing any work.
Multi-workspace support. If you install the app in more than one workspace, key everything by team_id and store per-team bot tokens. Do not rely on a single SLACK_EVENTS_TOKEN in .env beyond development.
Local development. Expose your app with ngrok or a similar tunnel, update the Request URL in the Slack dashboard, and use the dashboard’s event delivery log plus tail -f storage/logs/laravel.log to watch requests. Configure the route middleware as api (or exclude it from CSRF) so Slack’s POST is not rejected.
X-Slack-Signature and X-Slack-Request-Timestamp. The signature is computed over the raw request body, so middleware that rewrites or parses the body can break verification. Confirm which scheme your installed version supports; older releases may still expect the deprecated verification token.200 quickly, Slack retries with X-Slack-Retry-Num. Combined with at-least-once delivery, this means the same event_id can arrive multiple times. Add a unique index on event_id or check for an existing row before handling.config/slack-events.php. After editing it, run php artisan config:clear or php artisan config:cache; otherwise your new handler silently never runs.url_verification must stay reachable. If you add auth middleware or a global request filter, make sure the verification challenge still gets through, or Slack will refuse to save the Request URL.message.channels in the dashboard is not enough; the bot token needs the corresponding scope and the bot must be in the channel.X-Slack-Retry-Num header on every request during bring-up. The Slack dashboard shows failed deliveries but not your application’s stack trace.slack_events table is often less work than patching it.How can I help you explore Laravel packages today?