Search by

alexanderpoellmann / laravel-zendesk

AlexanderPoellmann

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

Homepage

pkg:composer/alexanderpoellmann/laravel-zendesk

Statistics

Installs: 971

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 1

v0.1.1 2025-11-03 11:37 UTC

This package is auto-updated.

Last update: 2026-09-25 09:22:28 UTC


README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Quality Action Status Total Downloads

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:

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:

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.