shelfwood / laravel-n8n
Tag-based n8n webhook integration for Laravel — fire events, auto-dispatch to matching n8n workflows
Requires
- php: ^8.3
- illuminate/contracts: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- illuminate/queue: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^10.0
- pestphp/pest: ^4.0
Suggests
- filament/filament: Required for the N8nStatus admin page (^5.0)
README
Tag-based n8n webhook integration for Laravel. Fire events in your app, auto-dispatch to matching n8n workflows. Zero configuration per workflow — just tag your n8n workflows and the package handles the rest.
How it works
- Add the
HasN8nTriggertrait to any Laravel Event class - The event auto-generates a tag from its class name (e.g.
OrderCompleted→app:order-completed) - When the event fires, the package finds all active n8n workflows tagged with
app:order-completed - It POSTs the event payload to each workflow's webhook URL
- Fire-and-forget — your app doesn't depend on n8n being available
Installation
composer require shelfwood/laravel-n8n
The service provider auto-discovers. Publish the config:
php artisan vendor:publish --tag=n8n-config
Add to your .env:
N8N_URL=https://your-n8n-instance.com N8N_API_KEY=your-api-key
Usage
1. Create an event with the trait
use Illuminate\Foundation\Events\Dispatchable; use Shelfwood\N8n\Traits\HasN8nTrigger; class OrderCompleted { use Dispatchable, HasN8nTrigger; public function __construct( public string $orderId, public float $total, ) {} public function toArray(): array { return [ 'order_id' => $this->orderId, 'total' => $this->total, ]; } }
2. Fire the event normally
event(new OrderCompleted($order->id, $order->total));
3. Create an n8n workflow with matching tag
In your n8n instance, create a workflow with:
- A Webhook trigger node
- A tag matching the event:
app:order-completed
The package finds the workflow by tag and POSTs:
{
"event": "App\\Events\\OrderCompleted",
"timestamp": "2026-04-13T00:00:00+00:00",
"tags": ["app:order-completed"],
"data": {
"order_id": "abc-123",
"total": 99.95
}
}
Custom tags
Override the auto-generated tag:
$event = new OrderCompleted($id, $total); $event->setN8nTags(['custom:high-value-order']); event($event);
Filament Status Page
If your project uses Filament, register the status page in your AdminPanelProvider:
use Shelfwood\N8n\Filament\Pages\N8nStatus; ->pages([ N8nStatus::class, ])
This shows:
- n8n connection health
- All discovered events with their tags
- Which n8n workflows are connected to which events
Configuration
// config/n8n.php return [ 'api' => [ 'url' => env('N8N_URL', ''), 'key' => env('N8N_API_KEY', ''), ], 'workflows' => [ 'timeout' => (int) env('N8N_WORKFLOW_TIMEOUT', 10), 'retry_attempts' => 3, 'retry_delay' => 5, // Cache TTL for the workflow list (seconds). Every event dispatch // would otherwise refetch the full workflow set. 0 disables. 'cache_ttl' => (int) env('N8N_WORKFLOWS_CACHE_TTL', 60), ], // Tag prefix for auto-generated event tags. Override per environment to // discriminate workflows on a single shared n8n (`staging:`, `prod:`). 'tag_prefix' => env('N8N_TAG_PREFIX', 'app:'), // Directories scanned by the Filament status page 'event_directories' => [ 'app/Events', ], ];
Domain-driven layouts
If your application stores events outside app/ (e.g. a src/Domain/*/Events
layout under a Domain\ PSR-4 root), override event_directories:
'event_directories' => [ 'src/Domain/*/Events', ],
The status page resolves class names from composer.json PSR-4, so any
namespace mapped there works — no extra registration needed.
Multi-environment tag separation
Two apps sharing one n8n? Set N8N_TAG_PREFIX=staging: on staging and
N8N_TAG_PREFIX=prod: on production. Tag your workflows accordingly and the
two streams stay isolated without duplicated workflows.
Graceful degradation
- No
N8N_URL→ events fire normally but no webhooks are dispatched - n8n is down → the queued job retries 3 times, then logs and moves on
- No matching workflow → the job silently returns (no error)
Testing
In your test suite, set N8N_URL to empty in phpunit.xml:
<env name="N8N_URL" value=""/>
This disables webhook dispatch. Your n8n-specific tests can override:
config(['n8n.api.url' => 'https://n8n.test']); Queue::fake(); event(new OrderCompleted('123', 50.00)); Queue::assertPushed(DispatchN8nWebhook::class);
License
MIT