PHPackages                             laulamanapps/apple-passbook-laravel - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. laulamanapps/apple-passbook-laravel

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

laulamanapps/apple-passbook-laravel
===================================

Generate Apple Wallet passes (PassKit) from your Laravel application

v1.0.0(1mo ago)032MITPHPPHP ^8.1CI passing

Since Jul 8Pushed 1mo agoCompare

[ Source](https://github.com/LauLamanApps/apple-passbook-laravel)[ Packagist](https://packagist.org/packages/laulamanapps/apple-passbook-laravel)[ RSS](/packages/laulamanapps-apple-passbook-laravel/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (6)Versions (2)Used By (0)

Apple Passbook Laravel
======================

[](#apple-passbook-laravel)

This package provides Laravel integration for the [LauLamanApps Apple Passbook Package](https://github.com/LauLamanApps/apple-passbook): generate Apple Wallet passes (`.pkpass` files) and serve the Apple PassKit [Web Service](https://developer.apple.com/documentation/walletpasses/adding-a-web-service-to-update-passes) endpoints from your Laravel application.

Requirements
------------

[](#requirements)

- PHP 8.1+
- Laravel 11.x, 12.x or 13.x

Installation
------------

[](#installation)

```
composer require laulamanapps/apple-passbook-laravel
```

The service provider (`LauLamanApps\ApplePassbookLaravel\ApplePassbookServiceProvider`) is registered automatically via Laravel package auto-discovery.

Run Tests
---------

[](#run-tests)

```
composer install
./bin/phpunit
```

Configuration
-------------

[](#configuration)

Publish the config file:

```
php artisan vendor:publish --tag=apple-passbook-config
```

This creates `config/apple-passbook.php`:

KeyDefaultDescription`certificate``env('APPLE_PASSBOOK_CERTIFICATE')`Path to your `.p12` Pass Type ID certificate`certificate_password``env('APPLE_PASSBOOK_CERTIFICATE_PASSWORD')`Password protecting the `.p12` file`apple_wwdr_ca``env('APPLE_PASSBOOK_WWDR_CA')` (null)Optional path to an Apple WWDR CA `.pem`; `null` uses the library default`web_service.enabled``env('APPLE_PASSBOOK_WEB_SERVICE_ENABLED', true)`Whether the PassKit web service routes are registered`web_service.route_prefix``env('APPLE_PASSBOOK_WEB_SERVICE_ROUTE_PREFIX', '/v1')`Route prefix for the web service endpointsAdd the ENV variables to your `.env` file:

```
###> laulamanapps/apple-passbook-laravel ###
APPLE_PASSBOOK_CERTIFICATE=/path/to/certificates/pass.p12
APPLE_PASSBOOK_CERTIFICATE_PASSWORD=password
###< laulamanapps/apple-passbook-laravel ###
```

Get Certificate
---------------

[](#get-certificate)

Head over to the [Apple Developer Portal](https://developer.apple.com/account/resources/certificates/list) to get yourself a certificate to sign your passbooks with.

Export the certificate and key to a `.p12` file using **Keychain Access**.

Generating a Pass
-----------------

[](#generating-a-pass)

The package registers `LauLamanApps\ApplePassbook\Build\Compiler` as a singleton, configured with your certificate. Inject it anywhere:

```
namespace App\Http\Controllers;

use LauLamanApps\ApplePassbook\Build\Compiler;
use LauLamanApps\ApplePassbook\GenericPassbook;
use LauLamanApps\ApplePassbook\MetaData\Barcode;
use LauLamanApps\ApplePassbook\Style\BarcodeFormat;

final class PassbookController
{
    public function __construct(
        private readonly Compiler $compiler,
    ) {
    }

    public function download()
    {
        $passbook = new GenericPassbook('8j23fm3');
        $passbook->setTeamIdentifier('');
        $passbook->setPassTypeIdentifier('');
        $passbook->setOrganizationName('Toy Town');
        $passbook->setDescription('Toy Town Membership');

        $barcode = new Barcode();
        $barcode->setFormat(BarcodeFormat::Pdf417);
        $barcode->setMessage('123456789');
        $passbook->setBarcode($barcode);

        return response($this->compiler->compile($passbook), 200, [
            'Content-Description' => 'File Transfer',
            'Content-Type' => 'application/vnd.apple.pkpass',
            'Content-Disposition' => 'filename="passbook.pkpass"',
        ]);
    }
}
```

See the [core package documentation](https://github.com/LauLamanApps/apple-passbook) for all pass types (event ticket, coupon, boarding pass, store card, generic), fields, images, colors and locations.

PassKit Web Service
-------------------

[](#passkit-web-service)

When `web_service.enabled` is `true` the package registers all Apple PassKit [Web Service](https://developer.apple.com/documentation/walletpasses/adding-a-web-service-to-update-passes) endpoints under the configured prefix (default `/v1`):

MethodURIPurpose`POST``/v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}/{serialNumber}`Register a device for pass updates`DELETE``/v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}/{serialNumber}`Unregister a device`GET``/v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}`Serial numbers of updated passes`GET``/v1/passes/{passTypeIdentifier}/{serialNumber}`Retrieve the latest version of a pass`POST``/v1/log`Device diagnostic loggingFor the web service to work, the pass itself must point at your application:

```
$passbook->setWebService('https://example.com/', $authenticationToken);
```

### Events

[](#events)

The controllers delegate all persistence decisions to your application through mutable event objects. Each event starts with `Status::Unhandled` — your listener must set the appropriate status, otherwise a `LogicException` is thrown.

EventDispatched when`DeviceRegisteredEvent`A device registers for pass updates`DeviceUnregisteredEvent`A device unregisters from pass updates`DeviceRequestUpdatedPassesEvent`A device requests the serial numbers of updated passes (`passesUpdatedSince` is available via `getPassesUpdatedSince()`)`RetrieveUpdatedPassbookEvent`A device requests an updated pass (`If-Modified-Since` is available via `getUpdatedSince()`)The `/v1/log` endpoint needs no listener: messages are written to the application logger at `info` level.

### Authenticating requests

[](#authenticating-requests)

Devices authenticate with an `Authorization: ApplePass ` header carrying the token you set on the pass with `setWebService()`. The token-carrying events expose `isAuthenticatedBy(string $expectedToken): bool`.

> **Always use `$event->isAuthenticatedBy(...)` — never compare `$event->getAuthenticationToken()` with `!==`.**`isAuthenticatedBy()` uses PHP's `hash_equals()`, which compares the two tokens in constant time. A plain string comparison short-circuits on the first differing byte, which leaks timing information an attacker can use to reconstruct a valid token byte by byte.

### Example listeners

[](#example-listeners)

```
namespace App\Listeners;

use App\Models\Pass;
use DateTimeImmutable;
use LauLamanApps\ApplePassbookLaravel\Events\DeviceRegisteredEvent;

final class RegisterDevice
{
    public function handle(DeviceRegisteredEvent $event): void
    {
        $pass = Pass::where('serial_number', $event->getSerialNumber())->first();

        if ($pass === null || !$event->isAuthenticatedBy($pass->getAuthToken())) {
            $event->notAuthorized();

            return;
        }

        $registration = $pass->registrations()->firstOrCreate(
            ['device_library_identifier' => $event->getDeviceLibraryIdentifier()],
            ['push_token' => $event->getPushToken()],
        );

        if (!$registration->wasRecentlyCreated) {
            $event->alreadyRegistered();

            return;
        }

        $event->deviceRegistered();
    }
}
```

```
namespace App\Listeners;

use App\Models\Pass;
use LauLamanApps\ApplePassbookLaravel\Events\DeviceUnregisteredEvent;

final class UnregisterDevice
{
    public function handle(DeviceUnregisteredEvent $event): void
    {
        $pass = Pass::where('serial_number', $event->getSerialNumber())->first();

        if ($pass === null || !$event->isAuthenticatedBy($pass->getAuthToken())) {
            $event->notAuthorized();

            return;
        }

        $pass->registrations()
            ->where('device_library_identifier', $event->getDeviceLibraryIdentifier())
            ->delete();

        $event->deviceUnregistered();
    }
}
```

```
namespace App\Listeners;

use App\Models\Pass;
use LauLamanApps\ApplePassbookLaravel\Events\DeviceRequestUpdatedPassesEvent;

final class ListUpdatedPasses
{
    public function handle(DeviceRequestUpdatedPassesEvent $event): void
    {
        $passes = Pass::registeredTo($event->getDeviceLibraryIdentifier())
            ->where('pass_type_identifier', $event->getPassTypeIdentifier())
            ->when($event->getPassesUpdatedSince(), fn ($query, $since) => $query->where('updated_at', '>', $since))
            ->get();

        if ($passes->isEmpty()) {
            $event->notFound();

            return;
        }

        $event->setSerialNumbers(
            $passes->pluck('serial_number')->all(),
            $passes->max('updated_at')->toDateTimeImmutable(),
        );
    }
}
```

```
namespace App\Listeners;

use App\Models\Pass;
use LauLamanApps\ApplePassbookLaravel\Events\RetrieveUpdatedPassbookEvent;

final class RetrieveUpdatedPassbook
{
    public function handle(RetrieveUpdatedPassbookEvent $event): void
    {
        $pass = Pass::where('serial_number', $event->getSerialNumber())->first();

        if ($pass === null) {
            $event->notFound();

            return;
        }

        if (!$event->isAuthenticatedBy($pass->getAuthToken())) {
            $event->notAuthorized();

            return;
        }

        $updatedAt = $pass->updated_at->toDateTimeImmutable();

        if ($event->getUpdatedSince() !== null && $updatedAt getUpdatedSince()) {
            $event->notModified();

            return;
        }

        $event->setPassbook($pass->toPassbook(), $updatedAt);
    }
}
```

Since Laravel 11, listeners in `app/Listeners` are auto-discovered — no manual registration is required. The compiled pass is returned to the device as `application/vnd.apple.pkpass` with a `Last-Modified` header, and `304 Not Modified` is honored via the `If-Modified-Since` request header.

Credits
-------

[](#credits)

This package has been developed by [LauLaman](https://github.com/LauLaman).

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance90

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

49d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/930ca3b1756d00a63c8ff3363e81f16f961cf3ef79ff0c9d362670c36d97e456?d=identicon)[LauLaman](/maintainers/LauLaman)

---

Top Contributors

[![LauLaman](https://avatars.githubusercontent.com/u/8283992?v=4)](https://github.com/LauLaman "LauLaman (4 commits)")

---

Tags

phplaravelappleiphonewalletiospassbookstore cardcouponevent ticketboarding passiPod Touchpkpass

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/laulamanapps-apple-passbook-laravel/health.svg)

```
[![Health](https://phpackages.com/badges/laulamanapps-apple-passbook-laravel/health.svg)](https://phpackages.com/packages/laulamanapps-apple-passbook-laravel)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

80427.1M249](/packages/laravel-mcp)[api-platform/laravel

API Platform support for Laravel

58190.1k21](/packages/api-platform-laravel)[forjedio/inertia-table

Backend-driven dynamic tables for Laravel + Inertia.js

272.0k](/packages/forjedio-inertia-table)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
