alexanderpoellmann / laravel-zendesk
A Laravel wrapper for the Zendesk Support API client with first-class anonymous request and ticket management workflows
Package info
github.com/AlexanderPoellmann/laravel-zendesk
pkg:composer/alexanderpoellmann/laravel-zendesk
Requires
- php: ^8.4
- illuminate/contracts: ^11.0||^12.0
- spatie/laravel-data: ^4.17
- spatie/laravel-package-tools: ^1.16
- zendesk/zendesk_api_client_php: ^4.1
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- spatie/laravel-ray: ^1.35
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A Laravel 12/13 wrapper around the Zendesk Support API client, with first-class helpers for anonymous contact-form requests and authenticated ticket management.
The package deliberately keeps anonymous and authenticated Zendesk clients separate so an authenticated backend call cannot leak an Authorization header into a later anonymous request.
Installation
composer require alexanderpoellmann/laravel-zendesk
Publish the configuration when you want to customise it:
php artisan vendor:publish --tag="laravel-zendesk-config"
Or alternatively, add the following entry to your config/services.php file:
return [ 'zendesk' => [ 'subdomain' => env('ZENDESK_SUBDOMAIN'), 'username' => env('ZENDESK_USERNAME'), 'token' => env('ZENDESK_TOKEN'), 'auth' => env('ZENDESK_AUTH_METHOD', 'auto'), ], ];
Configuration
For anonymous requests, only ZENDESK_SUBDOMAIN is required. Authenticated operations additionally need either API-token or OAuth credentials.
ZENDESK_SUBDOMAIN=your-subdomain # API-token authentication ZENDESK_AUTH_METHOD=basic ZENDESK_USERNAME=agent@example.com ZENDESK_TOKEN=your-api-token # Optional HTTP settings ZENDESK_CONNECT_TIMEOUT=5 ZENDESK_TIMEOUT=30
For OAuth, omit the username and set:
ZENDESK_AUTH_METHOD=oauth ZENDESK_TOKEN=your-oauth-access-token
ZENDESK_AUTH_METHOD=auto is the default. It selects API-token authentication when a username is configured and OAuth otherwise.
Existing installations using services.zendesk.subdomain, services.zendesk.username, and services.zendesk.token continue to work as a backwards-compatible fallback. When laravel-zendesk.auth is unset, services.zendesk.auth is used, falling back to auto. An explicit package authentication setting takes precedence, including auto.
The published config is:
return [ 'subdomain' => env('ZENDESK_SUBDOMAIN'), 'username' => env('ZENDESK_USERNAME'), 'token' => env('ZENDESK_TOKEN'), // Null defers to services.zendesk.auth, then automatic authentication. 'auth' => env('ZENDESK_AUTH_METHOD'), 'scheme' => env('ZENDESK_SCHEME', 'https'), 'hostname' => env('ZENDESK_HOSTNAME', 'zendesk.com'), 'port' => (int) env('ZENDESK_PORT', 443), 'connect_timeout' => (float) env('ZENDESK_CONNECT_TIMEOUT', 5), 'timeout' => (float) env('ZENDESK_TIMEOUT', 30), ];
Anonymous contact forms
Zendesk's Requests API supports anonymous ticket creation when your Zendesk account allows it. Anonymous API requests must not send an Authorization header. If you also accept anonymous attachments, the Zendesk setting that requires authentication for the request/uploads APIs must be disabled.
See the Zendesk documentation for the current account settings and email-verification behaviour:
- https://developer.zendesk.com/documentation/ticketing/managing-tickets/creating-and-managing-requests/#creating-anonymous-requests
- https://developer.zendesk.com/api-reference/ticketing/tickets/ticket-requests/#create-request
A typical Livewire/Vue contact-form flow is:
use AlexanderPoellmann\LaravelZendesk\Facades\Zendesk; $uploadTokens = []; if ($uploadedFile !== null) { $upload = Zendesk::uploadAnonymousAttachment( filePath: $uploadedFile->getRealPath(), mimeType: $uploadedFile->getMimeType(), fileName: $uploadedFile->getClientOriginalName(), ); $uploadTokens[] = $upload->token; } $request = Zendesk::createAnonymousRequest( firstName: 'John', lastName: 'Doe', email: 'john@example.com', recipientEmailAddress: 'support@example.com', subject: 'Help!', body: 'My printer is on fire!', uploads: $uploadTokens, locale: 'de-AT', );
Upload tokens are sent under comment.uploads, as required by Zendesk.
For advanced request fields, use the data object directly:
use AlexanderPoellmann\LaravelZendesk\Data\AnonymousRequestData; use AlexanderPoellmann\LaravelZendesk\Data\AnonymousRequesterData; $request = Zendesk::createAnonymousRequestFromData(new AnonymousRequestData( requester: new AnonymousRequesterData( firstName: 'John', lastName: 'Doe', email: 'john@example.com', locale: 'de-AT', ), recipientEmailAddress: null, subject: 'Help!', body: 'Please contact me.', customFields: [ ['id' => 123456789, 'value' => 'website'], ], ticketFormId: 987654321, ));
Authenticated ticket management
The Tickets API is intended for agent/admin workflows.
use AlexanderPoellmann\LaravelZendesk\Facades\Zendesk; use AlexanderPoellmann\LaravelZendesk\Enums\Priorities; $ticket = Zendesk::createTicket( subject: 'Customer reported an issue', body: 'Initial agent comment.', priority: Priorities::High, additionalAttributes: [ 'tags' => ['website', 'priority-customer'], ], ); $ticket = Zendesk::findTicket($ticket->id); $page = Zendesk::listTickets([ 'page[size]' => 25, ]); $ticket = Zendesk::updateTicket($ticket->id, [ 'status' => 'pending', 'comment' => [ 'body' => 'Waiting for more information.', 'public' => false, ], ]); Zendesk::deleteTicket($ticket->id);
Creation helpers preserve nested additionalAttributes['comment'] options, including public: false for private ticket comments and author_id. The explicit body and any nonempty upload-token list take precedence over the corresponding additional comment fields. Other explicit payload fields, such as subject and priority, also take precedence.
The convenience helpers intentionally accept an array for ticket updates because Zendesk exposes a large and evolving ticket schema, including custom statuses and custom fields. Validate/authorise those attributes in your Filament action or application layer before passing them to the package.
For large ticket collections, use the upstream iterator instead of loading all pages yourself:
foreach (Zendesk::authenticatedClient()->tickets()->iterator(['page[size]' => 100]) as $ticket) { // ... }
Users and attachments
$user = Zendesk::createOrUpdateUser( firstName: 'John', lastName: 'Doe', email: 'john@example.com', ); $upload = Zendesk::uploadAuthenticatedAttachment( filePath: storage_path('app/report.pdf'), mimeType: 'application/pdf', fileName: 'report.pdf', );
uploadAttachment() remains an alias for an anonymous upload for backwards compatibility. Prefer the explicit uploadAnonymousAttachment() or uploadAuthenticatedAttachment() methods in new code.
Dependency injection, helper, and facade
All three styles are supported:
use AlexanderPoellmann\LaravelZendesk\Facades\Zendesk as ZendeskFacade; use AlexanderPoellmann\LaravelZendesk\Zendesk; // Dependency injection (preferred in application services) final class SyncTicket { public function __construct(private Zendesk $zendesk) {} public function __invoke(int $id): void { $ticket = $this->zendesk->findTicket($id); } } // Helper $zendesk = zendesk(); // Facade $ticket = ZendeskFacade::findTicket(123);
For raw upstream-client access:
// Explicit clients are preferred. $anonymousClient = zendesk()->anonymousClient(); $authenticatedClient = zendesk()->authenticatedClient(); // Backwards-compatible fluent syntax. Authentication applies to the next // proxied client call only, preventing state from leaking into later calls. $tickets = zendesk()->authenticate()->tickets();
Errors
API/client failures are not silently converted to null. Package operations throw ZendeskException and retain the original exception as getPrevious().
Malformed entries in a ticket list also raise ZendeskException; the package does not silently return a partial page.
use AlexanderPoellmann\LaravelZendesk\Exceptions\ZendeskException; try { $ticket = Zendesk::findTicket(123); } catch (ZendeskException $exception) { report($exception); // Render an application-appropriate message or retry policy. }
This is especially important for ticket creation: blindly retrying state-changing requests can create duplicate tickets. The package keeps the upstream client's conservative retry middleware and adds explicit connection/response timeouts; application-level retries should be intentional.
Replacing the client factory
Actions depend on the ZendeskClientFactory contract. You may replace the implementation in your own service provider, which is useful for custom transport requirements or a future upstream-client migration:
use AlexanderPoellmann\LaravelZendesk\Contracts\ZendeskClientFactory; $this->app->scoped(ZendeskClientFactory::class, CustomZendeskClientFactory::class);
Upstream client status
This package currently wraps zendesk/zendesk_api_client_php 4.x. Zendesk's PHP API-client documentation currently marks the official PHP client as unsupported/unmaintained. The wrapper isolates client construction behind a contract to reduce coupling, but consumers should still account for that upstream maintenance risk.
Upstream references:
- https://developer.zendesk.com/documentation/ticketing/api-clients/php/
- https://github.com/zendesk/zendesk_api_client_php
Testing and quality checks
composer test
composer analyse
composer format:test
Changelog
Please see CHANGELOG for recent changes.
License
The MIT License (MIT). See LICENSE.md.