|
| 1 | +# PHP n8n Client |
| 2 | + |
| 3 | +A lightweight, strongly typed PHP client for triggering n8n webhooks and tracking workflow executions. |
| 4 | + |
| 5 | +The package is intentionally small today. It focuses on the workflow path most PHP apps need first: |
| 6 | + |
| 7 | +- trigger an n8n webhook from application code; |
| 8 | +- read a typed webhook response; |
| 9 | +- extract an execution reference when n8n returns one; |
| 10 | +- fetch and poll execution status through the n8n API; |
| 11 | +- attach lifecycle hooks for logging, metrics, persistence, tracing, or framework integration. |
| 12 | + |
| 13 | +## Requirements |
| 14 | + |
| 15 | +- PHP 8.2 or newer |
| 16 | +- A PSR-18 HTTP client |
| 17 | +- PSR-17 request and stream factories |
| 18 | +- An n8n webhook URL |
| 19 | +- An n8n API key only when fetching or polling executions |
| 20 | + |
| 21 | +This package depends on PSR interfaces instead of a concrete HTTP client. Install implementations that match your application stack. |
| 22 | +URLs accepted by the package are PSR-7 `UriInterface` instances; create them with your PSR-17 URI factory or your framework's PSR-7 URI implementation. |
| 23 | + |
| 24 | +## Installation |
| 25 | + |
| 26 | +```bash |
| 27 | +composer require php-n8n/client guzzlehttp/guzzle nyholm/psr7 |
| 28 | +``` |
| 29 | + |
| 30 | +## Quick Start |
| 31 | + |
| 32 | +```php |
| 33 | +<?php |
| 34 | + |
| 35 | +declare(strict_types=1); |
| 36 | + |
| 37 | +use GuzzleHttp\Client as GuzzleClient; |
| 38 | +use Nyholm\Psr7\Factory\Psr17Factory; |
| 39 | +use PhpN8n\Client\Config\ApiConfig; |
| 40 | +use PhpN8n\Client\N8nClient; |
| 41 | +use PhpN8n\Client\Webhooks\Webhook; |
| 42 | +use PhpN8n\Client\Webhooks\WebhookRequest; |
| 43 | + |
| 44 | +$httpClient = new GuzzleClient(); |
| 45 | +$psr17Factory = new Psr17Factory(); |
| 46 | + |
| 47 | +$client = new N8nClient( |
| 48 | + httpClient: $httpClient, |
| 49 | + requestFactory: $psr17Factory, |
| 50 | + streamFactory: $psr17Factory, |
| 51 | + apiConfig: new ApiConfig( |
| 52 | + apiUri: $psr17Factory->createUri('https://n8n.example.com/api/v1'), |
| 53 | + apiKey: $_ENV['N8N_API_KEY'] ?? null, |
| 54 | + ), |
| 55 | +); |
| 56 | + |
| 57 | +$webhook = Webhook::fromUri( |
| 58 | + $psr17Factory->createUri('https://n8n.example.com/webhook/order-created') |
| 59 | +); |
| 60 | + |
| 61 | +$response = $client->workflow()->trigger( |
| 62 | + $webhook, |
| 63 | + WebhookRequest::create([ |
| 64 | + 'orderId' => 'ORD-1001', |
| 65 | + 'total' => 129.50, |
| 66 | + ]), |
| 67 | +); |
| 68 | + |
| 69 | +$executionReference = $response->executionReference(); |
| 70 | + |
| 71 | +if ($executionReference !== null) { |
| 72 | + $result = $client->execution($executionReference)->wait(); |
| 73 | + |
| 74 | + if ($result->status()->isSuccessful()) { |
| 75 | + // Continue after n8n finishes successfully. |
| 76 | + } |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +## Triggering Webhooks |
| 81 | + |
| 82 | +Create a `Webhook` from the n8n webhook URL, then send an optional `WebhookRequest`. |
| 83 | + |
| 84 | +```php |
| 85 | +use PhpN8n\Client\Webhooks\Webhook; |
| 86 | +use PhpN8n\Client\Webhooks\WebhookMethod; |
| 87 | +use PhpN8n\Client\Webhooks\WebhookRequest; |
| 88 | + |
| 89 | +$webhook = Webhook::fromUri($psr17Factory->createUri('https://n8n.example.com/webhook/sync-customer')) |
| 90 | + ->withMethod(WebhookMethod::Post) |
| 91 | + ->withHeader('X-App-Source', 'billing') |
| 92 | + ->withQueryParameter('tenant', 'acme'); |
| 93 | + |
| 94 | +$request = WebhookRequest::create(['customerId' => 123]) |
| 95 | + ->withHeader('X-Request-ID', 'req_123') |
| 96 | + ->withQueryParameter('dryRun', false); |
| 97 | + |
| 98 | +$response = $client->workflow()->trigger($webhook, $request); |
| 99 | +``` |
| 100 | + |
| 101 | +Request bodies may be arrays, scalar values, strings, or PSR-7 streams. Non-string, non-stream bodies are JSON encoded and receive `Content-Type: application/json` unless you already set a content type. |
| 102 | + |
| 103 | +## Webhook Responses |
| 104 | + |
| 105 | +`WebhookResponse` exposes: |
| 106 | + |
| 107 | +- `raw()` for the original PSR-7 response; |
| 108 | +- `statusCode()` for the HTTP status code; |
| 109 | +- `type()` as `Json`, `Text`, or `None`; |
| 110 | +- `body()` as decoded JSON, raw text, or `null`; |
| 111 | +- `executionReference()` when a response contains `executionId`, `execution_id`, `executionReference.id`, or `execution.id`; |
| 112 | +- `status()` when a response contains an n8n execution status. |
| 113 | + |
| 114 | +Invalid JSON returned with a JSON content type throws `InvalidWebhookResponseException`. |
| 115 | + |
| 116 | +## Tracking Executions |
| 117 | + |
| 118 | +Execution tracking uses the n8n API. Configure `ApiConfig` with the n8n API base URI and API key. |
| 119 | + |
| 120 | +```php |
| 121 | +use PhpN8n\Client\Config\ExecutionFetchOptions; |
| 122 | +use PhpN8n\Client\Config\PollingConfig; |
| 123 | +use PhpN8n\Client\Executions\ExecutionReference; |
| 124 | + |
| 125 | +$reference = ExecutionReference::fromId('123'); |
| 126 | + |
| 127 | +$result = $client->executions()->get( |
| 128 | + $reference, |
| 129 | + ExecutionFetchOptions::withData(redactExecutionData: false), |
| 130 | +); |
| 131 | + |
| 132 | +$result = $client->executions()->wait( |
| 133 | + $reference, |
| 134 | + new PollingConfig( |
| 135 | + timeoutSeconds: 60, |
| 136 | + intervalMilliseconds: 1000, |
| 137 | + fetchOptions: ExecutionFetchOptions::withData(), |
| 138 | + ), |
| 139 | +); |
| 140 | +``` |
| 141 | + |
| 142 | +Execution statuses are normalized to `ExecutionStatus` values: `New`, `Running`, `Success`, `Failed`, `Canceled`, `Waiting`, or `Unknown`. |
| 143 | + |
| 144 | +## Lifecycle Hooks |
| 145 | + |
| 146 | +Hooks are synchronous callbacks that receive a `HookContext`. They are useful for observability and application-specific side effects without coupling the package to a framework. |
| 147 | +`HookRegistry` is immutable, so `on()` returns a new registry with the callback registered. |
| 148 | + |
| 149 | +```php |
| 150 | +use PhpN8n\Client\Hooks\HookContext; |
| 151 | +use PhpN8n\Client\Hooks\HookRegistry; |
| 152 | +use PhpN8n\Client\Hooks\LifecycleHook; |
| 153 | + |
| 154 | +$hooks = (new HookRegistry()) |
| 155 | + ->on(LifecycleHook::BeforeWebhookRequest, function (HookContext $context): void { |
| 156 | + // Log request metadata or start a trace span. |
| 157 | + }) |
| 158 | + ->on(LifecycleHook::ExecutionStatusChanged, function (HookContext $context): void { |
| 159 | + // Persist status changes in your application. |
| 160 | + }) |
| 161 | + ->on(LifecycleHook::ExceptionThrown, function (HookContext $context): void { |
| 162 | + $exception = $context->exception(); |
| 163 | + // Report the exception to your monitoring system. |
| 164 | + }); |
| 165 | + |
| 166 | +$client = new N8nClient( |
| 167 | + httpClient: $httpClient, |
| 168 | + requestFactory: $psr17Factory, |
| 169 | + streamFactory: $psr17Factory, |
| 170 | + apiConfig: $apiConfig, |
| 171 | + hooks: $hooks, |
| 172 | +); |
| 173 | +``` |
| 174 | + |
| 175 | +Available hooks: |
| 176 | + |
| 177 | +- `BeforeWebhookRequest` |
| 178 | +- `AfterWebhookResponse` |
| 179 | +- `ExecutionReferenceCreated` |
| 180 | +- `BeforeExecutionRefresh` |
| 181 | +- `AfterExecutionRefresh` |
| 182 | +- `BeforePollingStarts` |
| 183 | +- `PollingAttempt` |
| 184 | +- `ExecutionStatusChanged` |
| 185 | +- `ExecutionSucceeded` |
| 186 | +- `ExecutionFailed` |
| 187 | +- `ExecutionTimedOut` |
| 188 | +- `ExceptionThrown` |
| 189 | + |
| 190 | +## Errors |
| 191 | + |
| 192 | +Package exceptions extend `PhpN8n\Client\Exceptions\N8nException`. |
| 193 | + |
| 194 | +- `AuthenticationException` for 401 responses; |
| 195 | +- `RequestException` for failed HTTP requests, non-2xx n8n responses, and invalid execution API payloads; |
| 196 | +- `InvalidWebhookResponseException` for malformed webhook JSON responses; |
| 197 | +- `ExecutionTimeoutException` when polling exceeds the configured timeout. |
| 198 | + |
| 199 | +## Quality Checks |
| 200 | + |
| 201 | +```bash |
| 202 | +composer check |
| 203 | +``` |
| 204 | + |
| 205 | +This runs Composer validation, PHPUnit, PSR-12 checks, and static analysis. |
| 206 | + |
| 207 | +## Current Scope |
| 208 | + |
| 209 | +This is not a full n8n API SDK yet. The current public scope is webhook execution and execution status tracking. The package is designed so additional n8n API areas can be added without replacing the webhook and hook APIs. |
0 commit comments