├── README.md ├── composer.json ├── config └── klaviyo.php ├── resources └── views │ ├── components │ ├── default.blade.php │ └── identify.blade.php │ ├── initialize.blade.php │ ├── object.blade.php │ └── recursive.blade.php └── src ├── Contracts ├── KlaviyoIdentity.php └── ViewedProduct.php ├── Jobs ├── SendKlaviyoIdentify.php └── SendKlaviyoTrack.php ├── Klaviyo.php ├── KlaviyoClient.php ├── LaravelKlaviyoServiceProvider.php ├── TrackEvent.php ├── UserEventSubscriber.php ├── View └── Creators │ └── InitializeCreator.php └── helpers.php /README.md: -------------------------------------------------------------------------------- 1 | # laravel-klaviyo 2 | 3 | This package assists with interacting with [Klaviyo](https://www.klaviyo.com/) to track client and server-side metrics and the REST api. 4 | 5 | ## Requirements 6 | 7 | For server-side track, identify or REST api calls this package utilises the Laravel HTTP Client. 8 | 9 | It is recommended that server-side events are processed in the background, by default jobs are placed on the `klaviyo` queue. 10 | 11 | ## Installation 12 | 13 | You can install the package via composer: 14 | 15 | ```bash 16 | composer require eonvisualmedia/laravel-klaviyo 17 | ``` 18 | 19 | The package will automatically register itself. 20 | 21 | You can optionally publish the config file with: 22 | 23 | ```bash 24 | php artisan vendor:publish --provider="EonVisualMedia\LaravelKlaviyo\LaravelKlaviyoServiceProvider" --tag="tags-config" 25 | ``` 26 | 27 | Depending upon your intended usage minimally you'll need to configure your environment with your public and private api keys. 28 | 29 | ``` 30 | // .env 31 | 32 | KLAVIYO_PRIVATE_API_KEY= 33 | KLAVIYO_PUBLIC_API_KEY= 34 | ``` 35 | 36 | ## Usage 37 | 38 | ### Basic Example 39 | 40 | First you'll need to include the Klaviyo JavaScript API for Identification and Tracking by including it at the end of your layout just before the closing body tag. 41 | 42 | ``` 43 | // layout.blade.php 44 | 45 | 46 | 47 | {{-- ... --}} 48 | @include('klaviyo::initialize') 49 | 50 | 51 | ``` 52 | 53 | #### To add identity 54 | 55 | If the current user is not identified and `Auth::user()` is an instance of `EonVisualMedia\LaravelKlaviyo\Contracts\KlaviyoIdentity` then the `getKlaviyoIdentity` method will be called and an identify 56 | event added to the page. 57 | 58 | Alternatively the identify method may be called explicitly, for instance after user login. 59 | 60 | ```php 61 | Klaviyo::identify([ 62 | 'email' => 'foo@example.com', 63 | 'first_name' => 'Foo', 64 | 'last_name' => 'Bar' 65 | ]); 66 | ``` 67 | 68 | #### Track events client-side 69 | 70 | ```php 71 | Klaviyo::push('track', 'Added to Cart', [ 72 | '$value' => 100, 73 | 'AddedTitle' => 'Widget A' 74 | ]); 75 | ``` 76 | 77 | #### Track events server-side: 78 | 79 | To queue server-side events. 80 | 81 | ```php 82 | Klaviyo::track(TrackEvent::make( 83 | 'Placed Order', 84 | [ 85 | 'unique_id' => '1234_WINNIEPOOH', 86 | 'value' => 9.99, 87 | ] 88 | )); 89 | ``` 90 | 91 | You can optionally also specify the customer properties and timestamp, if not specified the customer will attempt to be identified by their cookie ($exchange_id) or their user model if `Auth::user()` 92 | is an instance of `EonVisualMedia\LaravelKlaviyo\Contracts\KlaviyoIdentity`. 93 | 94 | ```php 95 | Klaviyo::track(TrackEvent::make( 96 | 'Placed Order', 97 | [ 98 | 'unique_id' => '1234_WINNIEPOOH', 99 | 'value' => 9.99, 100 | ], 101 | [ 102 | 'email' => 'foo@example.com', 103 | 'first_name' => 'Foo', 104 | 'last_name' => 'Bar', 105 | ], 106 | now()->addWeeks(-1) 107 | )); 108 | ``` 109 | 110 | ### Monitoring Login Events 111 | 112 | By default, the package will subscribe to `Illuminate\Auth\Events\Login` events and dispatch an `klaviyo.identify(...)` call. 113 | 114 | This behaviour can be disabled using the config option `identify_on_login`. 115 | 116 | ### Advanced usage 117 | 118 | #### Macros 119 | 120 | The package allows you to extend its functionality, this can be helpful for creating reusable events. 121 | 122 | You may define macros within the `boot` method of a service provider, either your own or within the application's `App\Providers\AppServiceProvider` class. 123 | 124 | ```php 125 | use EonVisualMedia\LaravelKlaviyo\Klaviyo; 126 | 127 | /** 128 | * Bootstrap any application services. 129 | * 130 | * @return void 131 | */ 132 | public function boot() 133 | { 134 | Klaviyo::macro( 135 | 'fulfilled_order', 136 | function (Transaction $transaction) { 137 | Klaviyo::track(TrackEvent::make( 138 | 'Fulfilled Order', 139 | $transaction->toKlaviyo(), 140 | $transaction->user, 141 | $transaction->created_at 142 | )); 143 | } 144 | ); 145 | } 146 | ``` 147 | 148 | With the macro defined you may invoke it anywhere in your application: 149 | 150 | ```php 151 | Klaviyo::fulfilled_order($transaction); 152 | ``` 153 | 154 | #### REST 155 | 156 | You may interact with the Klaviyo REST api using the Laravel HTTP Client, calls forwarded via KlaviyoClient append a `Authorization: Klaviyo-API-Key your-private-api-key` header to requests. 157 | 158 | ```php 159 | Klaviyo::get('lists'); 160 | 161 | Klaviyo::post("profile-subscription-bulk-create-jobs", [ 162 | 'data' => [ 163 | 'type' => 'profile-subscription-bulk-create-job', 164 | 'attributes' => [ 165 | 'profiles' => [ 166 | 'data' => [ 167 | [ 168 | 'type' => 'profile', 169 | 'attributes' => [ 170 | 'email' => 'foo@example.com', 171 | 'subscriptions' => [ 172 | 'email' => [ 173 | 'marketing' => [ 174 | 'consent' => 'SUBSCRIBED' 175 | ] 176 | ], 177 | ] 178 | ] 179 | ] 180 | ] 181 | ] 182 | ], 183 | 'relationships' => [ 184 | 'list' => [ 185 | 'data' => [ 186 | 'type' => 'list', 187 | 'id' => $list_id 188 | ] 189 | ] 190 | ] 191 | ] 192 | ]); 193 | 194 | Klaviyo::delete("profile-subscription-bulk-delete-jobs", [ 195 | 'data' => [ 196 | 'type' => 'profile-subscription-bulk-delete-job', 197 | 'attributes' => [ 198 | 'profiles' => [ 199 | 'data' => [ 200 | [ 201 | 'type' => 'profile', 202 | 'attributes' => [ 203 | 'email' => 'foo@example.com', 204 | ] 205 | ] 206 | ] 207 | ] 208 | ], 209 | 'relationships' => [ 210 | 'list' => [ 211 | 'data' => [ 212 | 'type' => 'list', 213 | 'id' => $list_id 214 | ] 215 | ] 216 | ] 217 | ] 218 | ]); 219 | ``` 220 | 221 | ## Upgrading from v1 222 | 223 | The [Klaviyo](https://www.klaviyo.com/) legacy v1/v2 APIs are scheduled to retire June 30th, 2024. 224 | 225 | I would encourage you to review especially the breaking changes on the [Klaviyo: API versioning and deprecation policy](https://developers.klaviyo.com/en/docs/api_versioning_and_deprecation_policy) 226 | 227 | The API changes have therefore necessitated a few breaking changes to this package, specifically the payloads required for `identify` and `push`. 228 | See the examples below, for example the `getKlaviyoIdentity` response replaces `$email` with `email`. 229 | 230 | Also take note that client and server payloads for identify/profile have a few differences `klaviyo_client_to_server_profile` may be of assistance help converting client payloads to server profiles. 231 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eonvisualmedia/laravel-klaviyo", 3 | "description": "Interact with the Klaviyo API from your Laravel app", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Michael Nightingale", 8 | "email": "mnightingale@eon-media.com" 9 | } 10 | ], 11 | "require": { 12 | "php": "^8.0", 13 | "guzzlehttp/guzzle": "^7.0", 14 | "illuminate/bus": "^8.0|^9.0|^10.0|^11.0", 15 | "illuminate/http": "^8.0|^9.0|^10.0|^11.0", 16 | "illuminate/support": "^8.0|^9.0|^10.0|^11.0" 17 | }, 18 | "require-dev": { 19 | "orchestra/testbench": "^7.0", 20 | "phpunit/phpunit": "^9.3" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "EonVisualMedia\\LaravelKlaviyo\\": "src" 25 | }, 26 | "files": [ 27 | "src/helpers.php" 28 | ] 29 | }, 30 | "autoload-dev": { 31 | "psr-4": { 32 | "EonVisualMedia\\LaravelKlaviyo\\Test\\": "tests" 33 | } 34 | }, 35 | "config": { 36 | "sort-packages": true 37 | }, 38 | "extra": { 39 | "laravel": { 40 | "providers": [ 41 | "EonVisualMedia\\LaravelKlaviyo\\LaravelKlaviyoServiceProvider" 42 | ], 43 | "aliases": { 44 | "Klaviyo": "EonVisualMedia\\LaravelKlaviyo\\Klaviyo" 45 | } 46 | } 47 | }, 48 | "minimum-stability": "dev", 49 | "prefer-stable": true 50 | } 51 | -------------------------------------------------------------------------------- /config/klaviyo.php: -------------------------------------------------------------------------------- 1 | (bool)env('KLAVIYO_ENABLED', true), 10 | 11 | 'endpoint' => env('KLAVIYO_ENDPOINT', 'https://a.klaviyo.com/api/'), 12 | 13 | 'api_version' => env('KLAVIYO_API_VERSION', '2024-05-15'), 14 | 15 | /** 16 | * The queue on which jobs will be processed. 17 | */ 18 | 'queue' => env('KLAVIYO_QUEUE', 'klaviyo'), 19 | 20 | 'private_api_key' => env('KLAVIYO_PRIVATE_API_KEY', ''), 21 | 22 | 'public_api_key' => env('KLAVIYO_PUBLIC_API_KEY', ''), 23 | 24 | 'identity_key_name' => env('KLAVIYO_IDENTITY_KEY_NAME', 'email'), 25 | 26 | /** 27 | * Key under which to flash data to the session. 28 | */ 29 | 'session_key' => env('KLAVIYO_SESSION_KEY', '_klaviyo'), 30 | 31 | /** 32 | * Push a klaviyo.identify(...) call after a \Illuminate\Auth\Events\Login event 33 | */ 34 | 'identify_on_login' => env('KLAVIYO_IDENTITY_ON_LOGIN', true), 35 | 36 | ]; 37 | -------------------------------------------------------------------------------- /resources/views/components/default.blade.php: -------------------------------------------------------------------------------- 1 | @switch(count($item)) 2 | @case(1) 3 | klaviyo.push([@json($item[0])]); 4 | @break 5 | @case(2) 6 | klaviyo.push([@json($item[0]), @json($item[1])]); 7 | @break 8 | @case(3) 9 | klaviyo.push([@json($item[0]), @json($item[1]), @json($item[2])]); 10 | @break 11 | @endswitch 12 | -------------------------------------------------------------------------------- /resources/views/components/identify.blade.php: -------------------------------------------------------------------------------- 1 | @if($slot->isEmpty()) 2 | klaviyo.identify(@json($item[1])); 3 | @else 4 | klaviyo.identify(@json($item[1]), function () { 5 | {{ $slot }} 6 | }); 7 | @endif 8 | -------------------------------------------------------------------------------- /resources/views/initialize.blade.php: -------------------------------------------------------------------------------- 1 | @if($enabled) 2 | 3 | @include('klaviyo::object') 4 | @if($data->isNotEmpty()) 5 | 8 | @endif 9 | @endif 10 | -------------------------------------------------------------------------------- /resources/views/object.blade.php: -------------------------------------------------------------------------------- 1 | {{-- https://developers.klaviyo.com/en/docs/introduction_to_the_klaviyo_object --}} 2 | 5 | -------------------------------------------------------------------------------- /resources/views/recursive.blade.php: -------------------------------------------------------------------------------- 1 | @foreach($items as $item) 2 | @if($item[0] === 'identify') 3 | 4 | @include('klaviyo::recursive', ['items' => $items->slice($loop->index + 1)]) 5 | 6 | @break 7 | @endif 8 | 9 | @endforeach 10 | -------------------------------------------------------------------------------- /src/Contracts/KlaviyoIdentity.php: -------------------------------------------------------------------------------- 1 | onQueue(config('klaviyo.queue')); 19 | } 20 | 21 | public function handle(KlaviyoClient $client) 22 | { 23 | $client 24 | ->post('profile-import', [ 25 | 'data' => [ 26 | 'type' => 'profile', 27 | 'attributes' => klaviyo_client_to_server_profile($this->attributes) 28 | ] 29 | ]) 30 | ->throw(function ($response, $exception) { 31 | Log::error("Klaviyo identify request failed", [ 32 | 'response' => $response->json(), 33 | ]); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Jobs/SendKlaviyoTrack.php: -------------------------------------------------------------------------------- 1 | onQueue(config('klaviyo.queue')); 34 | $this->events = $events; 35 | } 36 | 37 | public function handle(KlaviyoClient $client) 38 | { 39 | $requests = function (Pool $pool) use ($client) { 40 | foreach ($this->events as $event) { 41 | yield $pool 42 | ->acceptJson() 43 | ->asJson() 44 | ->withToken($client->getPrivateKey(), 'Klaviyo-API-Key') 45 | ->withHeaders([ 46 | 'revision' => $client->getApiVersion() 47 | ]) 48 | ->post($client->getEndpoint().'events', [ 49 | 'data' => $event->toPayload() 50 | ]); 51 | } 52 | }; 53 | 54 | $client->pool(function (Pool $pool) use ($requests) { 55 | Each::ofLimit( 56 | $requests($pool), 57 | $this->concurrency, 58 | fn(\Illuminate\Http\Client\Response $response, $index) => $response->throw(function (\Illuminate\Http\Client\Response $response, $exception) { 59 | Log::error("Klaviyo track request failed", [ 60 | 'response' => $response->json(), 61 | ]); 62 | }), 63 | )->wait(); 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Klaviyo.php: -------------------------------------------------------------------------------- 1 | endpoint = $config['endpoint'] ?? ''; 110 | $this->privateKey = $config['private_api_key'] ?? ''; 111 | $this->publicKey = $config['public_api_key'] ?? ''; 112 | $this->apiVersion = $config['api_version'] ?: throw new InvalidArgumentException('Invalid API Version'); 113 | $this->identityKeyName = $config['identity_key_name'] ?: throw new InvalidArgumentException('Invalid default identity key name'); 114 | $this->enabled = $config['enabled'] ?? true; 115 | 116 | $this->pushCollection = new Collection(); 117 | } 118 | 119 | /** 120 | * @return string 121 | */ 122 | public function getEndpoint(): string 123 | { 124 | return $this->endpoint; 125 | } 126 | 127 | /** 128 | * @return string 129 | */ 130 | public function getPrivateKey(): string 131 | { 132 | return $this->privateKey; 133 | } 134 | 135 | /** 136 | * @return string 137 | */ 138 | public function getPublicKey(): string 139 | { 140 | return $this->publicKey; 141 | } 142 | 143 | /** 144 | * @return string 145 | */ 146 | public function getApiVersion(): string 147 | { 148 | return $this->apiVersion; 149 | } 150 | 151 | /** 152 | * The key for the identity. 153 | * 154 | * @return string 155 | */ 156 | public function getIdentityKeyName(): string 157 | { 158 | return $this->identityKeyName; 159 | } 160 | 161 | /** 162 | * Check whether Klaviyo script rendering and server-side jobs is enabled. 163 | * 164 | * @return bool 165 | */ 166 | public function isEnabled(): bool 167 | { 168 | return $this->enabled; 169 | } 170 | 171 | /** 172 | * Enable Klaviyo script rendering and server-side jobs. 173 | */ 174 | public function enable() 175 | { 176 | $this->enabled = true; 177 | } 178 | 179 | /** 180 | * Disable Klaviyo script rendering and server-side jobs. 181 | */ 182 | public function disable() 183 | { 184 | $this->enabled = false; 185 | } 186 | 187 | /** 188 | * Submit a server-side track event to Klaviyo. 189 | * 190 | * @param TrackEvent ...$events 191 | * @return void 192 | */ 193 | public function track(TrackEvent ...$events) 194 | { 195 | if (! $this->isEnabled()) { 196 | return; 197 | } 198 | 199 | $events = collect($events) 200 | ->reject(fn($event) => empty($event->identity)); 201 | 202 | if ($events->isNotEmpty()) { 203 | dispatch(new SendKlaviyoTrack(...$events->all())); 204 | } 205 | } 206 | 207 | /** 208 | * Submit a server-side identify event to Klaviyo. 209 | * 210 | * @param KlaviyoIdentity|string|array|null $identity 211 | * @return void 212 | * 213 | * @throws InvalidArgumentException 214 | */ 215 | public function identify(KlaviyoIdentity|string|array $identity = null) 216 | { 217 | if (! $this->isEnabled()) { 218 | return; 219 | } 220 | 221 | $identity = $this->resolveIdentity($identity ?? Auth::user()); 222 | dispatch(new SendKlaviyoIdentify($identity)); 223 | } 224 | 225 | /** 226 | * Resolve identity or profile of user 227 | * 228 | * @throws InvalidArgumentException 229 | */ 230 | public function resolveIdentity(KlaviyoIdentity|string|array|null $identity = null): ?array 231 | { 232 | if ($identity === null && $this->isIdentified()) { 233 | return $this->getIdentity(); 234 | } 235 | 236 | $identity = with($identity ?? Auth::user(), function ($value) { 237 | if ($value instanceof KlaviyoIdentity) { 238 | return $value->getKlaviyoIdentity(); 239 | } elseif (is_string($value)) { 240 | return [$this->getIdentityKeyName() => $value]; 241 | } elseif (is_array($value)) { 242 | return $value; 243 | } else { 244 | return null; 245 | } 246 | }); 247 | 248 | if (empty(array_intersect_key($identity ?? [], array_flip($this->identifyAttributes)))) { 249 | throw new InvalidArgumentException( 250 | sprintf( 251 | 'Identify requires one of the following fields: %s', 252 | implode(', ', $this->identifyAttributes) 253 | ) 254 | ); 255 | } 256 | 257 | return $identity; 258 | } 259 | 260 | /** 261 | * Decode the __kla_id cookie. 262 | * 263 | * @return array 264 | */ 265 | public function getDecodedCookie(): array 266 | { 267 | return json_decode(base64_decode(Cookie::get('__kla_id', '')), true) ?? []; 268 | } 269 | 270 | /** 271 | * Does the \Illuminate\Http\Request cookie contain an $exchange_id? 272 | * 273 | * @return bool 274 | */ 275 | public function isIdentified(): bool 276 | { 277 | return $this->getIdentity() !== null; 278 | } 279 | 280 | /** 281 | * Retrieve the $exchange_id from cookie. 282 | * 283 | * @return array|null 284 | */ 285 | public function getIdentity(): ?array 286 | { 287 | if (! empty($value = Arr::get($this->getDecodedCookie(), '$exchange_id'))) { 288 | return ['_kx' => $value]; 289 | } elseif (! empty($value = Arr::get($this->getDecodedCookie(), '$email'))) { 290 | return ['email' => $value]; 291 | } else { 292 | return null; 293 | } 294 | } 295 | 296 | /** 297 | * @return Collection 298 | */ 299 | public function getPushCollection(): Collection 300 | { 301 | return $this->pushCollection; 302 | } 303 | 304 | /** 305 | * Push an event to be rendered by the client to the beginning of the collection. 306 | * 307 | * @throws InvalidArgumentException 308 | */ 309 | public function prepend(...$values) 310 | { 311 | if (count($values) === 0) { 312 | throw new InvalidArgumentException('Not enough arguments for prepend.'); 313 | } elseif (count($values) > 3) { 314 | throw new InvalidArgumentException('Too many arguments for prepend.'); 315 | } 316 | 317 | $this->pushCollection->prepend($values); 318 | } 319 | 320 | /** 321 | * Push an event to be rendered by the client. 322 | * 323 | * @throws InvalidArgumentException 324 | */ 325 | public function push(...$values) 326 | { 327 | if (count($values) === 0) { 328 | throw new InvalidArgumentException('Not enough arguments for push.'); 329 | } elseif (count($values) > 3) { 330 | throw new InvalidArgumentException('Too many arguments for push.'); 331 | } 332 | 333 | $this->pushCollection->push($values); 334 | } 335 | 336 | /** 337 | * Push a viewed product event to be rendered by the client. 338 | * 339 | * @param ViewedProduct $product 340 | * @return void 341 | * 342 | * @throws InvalidArgumentException 343 | */ 344 | public function pushViewed(ViewedProduct $product) 345 | { 346 | $item = $product->getViewedProductProperties(); 347 | $this->push('track', 'Viewed Product', $item); 348 | $this->push('trackViewedItem', $item); 349 | } 350 | 351 | protected function client(): \Illuminate\Http\Client\PendingRequest 352 | { 353 | return Http::baseUrl($this->getEndpoint()) 354 | ->acceptJson() 355 | ->asJson() 356 | ->withToken($this->privateKey, 'Klaviyo-API-Key') 357 | ->withHeaders([ 358 | 'revision' => $this->getApiVersion() 359 | ]); 360 | } 361 | 362 | public function __call($method, $parameters) 363 | { 364 | if (static::hasMacro($method)) { 365 | return $this->macroCall($method, $parameters); 366 | } 367 | 368 | return $this->forwardCallTo($this->client(), $method, $parameters); 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/LaravelKlaviyoServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 21 | $this->publishes([ 22 | __DIR__.'/../config/klaviyo.php' => config_path('klaviyo.php'), 23 | ], ['klaviyo', 'klaviyo-config']); 24 | } 25 | 26 | View::creator('klaviyo::initialize', InitializeCreator::class); 27 | 28 | Event::subscribe(UserEventSubscriber::class); 29 | } 30 | 31 | /** 32 | * Register any application services. 33 | * 34 | * @return void 35 | */ 36 | public function register() 37 | { 38 | $this->mergeConfigFrom( 39 | __DIR__.'/../config/klaviyo.php', 'klaviyo' 40 | ); 41 | 42 | $this->loadViewsFrom( 43 | __DIR__.'/../resources/views', 'klaviyo' 44 | ); 45 | 46 | $this->app->singleton(KlaviyoClient::class, function () { 47 | return new KlaviyoClient( 48 | $this->app['config']->get('klaviyo', []) 49 | ); 50 | }); 51 | 52 | $this->app->alias(KlaviyoClient::class, 'klaviyo'); 53 | 54 | $this->app->resolving(EncryptCookies::class, function (EncryptCookies $middleware) { 55 | $middleware->disableFor('__kla_id'); 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/TrackEvent.php: -------------------------------------------------------------------------------- 1 | id = Str::uuid()->toString(); 23 | $this->identity = Klaviyo::resolveIdentity($identity); 24 | $this->timestamp = $time ?? Carbon::now(); 25 | } 26 | 27 | public static function make( 28 | string $metric_name, 29 | array $payload = [], 30 | KlaviyoIdentity|string|array $identity = null, 31 | Carbon $timestamp = null 32 | ): TrackEvent 33 | { 34 | return new static($metric_name, $payload, $identity, $timestamp); 35 | } 36 | 37 | /** 38 | * @return string 39 | */ 40 | public function getEvent(): string 41 | { 42 | return $this->metric_name; 43 | } 44 | 45 | /** 46 | * @param string $event 47 | * @return TrackEvent 48 | */ 49 | public function setEvent(string $event): TrackEvent 50 | { 51 | $this->metric_name = $event; 52 | 53 | return $this; 54 | } 55 | 56 | /** 57 | * @return array|null 58 | */ 59 | public function getProperties(): array|null 60 | { 61 | return $this->payload; 62 | } 63 | 64 | /** 65 | * @param array|null $properties 66 | * @return TrackEvent 67 | */ 68 | public function setProperties(array|null $properties): TrackEvent 69 | { 70 | $this->payload = $properties; 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * @return KlaviyoIdentity|string|array|null 77 | */ 78 | public function getIdentity(): KlaviyoIdentity|string|array|null 79 | { 80 | return $this->identity; 81 | } 82 | 83 | /** 84 | * @param KlaviyoIdentity|string|array|null $identity 85 | * @return TrackEvent 86 | */ 87 | public function setIdentity(KlaviyoIdentity|string|array|null $identity): TrackEvent 88 | { 89 | $this->identity = Klaviyo::resolveIdentity($identity); 90 | 91 | return $this; 92 | } 93 | 94 | /** 95 | * @return Carbon|null 96 | */ 97 | public function getTimestamp(): Carbon|null 98 | { 99 | return $this->timestamp; 100 | } 101 | 102 | /** 103 | * @param Carbon|null $timestamp 104 | * @return TrackEvent 105 | * @deprecated $timestamp 106 | */ 107 | public function setTimestamp(Carbon $timestamp = null): TrackEvent 108 | { 109 | $this->timestamp = $timestamp; 110 | 111 | return $this; 112 | } 113 | 114 | public function toPayload(): array 115 | { 116 | return [ 117 | 'type' => 'event', 118 | 'attributes' => array_replace_recursive([ 119 | 'properties' => [], 120 | 'time' => $this->timestamp->toIso8601String(), 121 | 'unique_id' => $this->id, 122 | 'metric' => [ 123 | 'data' => [ 124 | 'type' => 'metric', 125 | 'attributes' => [ 126 | 'name' => $this->metric_name, 127 | ] 128 | ] 129 | ], 130 | 'profile' => [ 131 | 'data' => [ 132 | 'type' => 'profile', 133 | 'attributes' => klaviyo_client_to_server_profile($this->identity) 134 | ], 135 | ], 136 | ], $this->payload), 137 | ]; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/UserEventSubscriber.php: -------------------------------------------------------------------------------- 1 | user instanceof KlaviyoIdentity) { 21 | session()->flash(config('klaviyo.session_key').'.identify'); 22 | } 23 | } 24 | 25 | /** 26 | * Register the listeners for the subscriber. 27 | */ 28 | public function subscribe(Dispatcher $events): void 29 | { 30 | $events->listen( 31 | Login::class, 32 | [UserEventSubscriber::class, 'handleUserLogin'] 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/View/Creators/InitializeCreator.php: -------------------------------------------------------------------------------- 1 | sessionKey = $config->get('klaviyo.session_key'); 18 | } 19 | 20 | public function create(View $view) 21 | { 22 | if ($this->shouldIdentify()) { 23 | $user = Auth::user(); 24 | if ($user instanceof KlaviyoIdentity) { 25 | $this->client->prepend('identify', $user->getKlaviyoIdentity()); 26 | } 27 | } 28 | 29 | $view 30 | ->with('enabled', $this->client->isEnabled()) 31 | ->with('publicKey', $this->client->getPublicKey()) 32 | ->with('data', $this->client->getPushCollection()); 33 | } 34 | 35 | protected function shouldIdentify(): bool 36 | { 37 | if (session()->has($this->sessionKey.'.identify')) { 38 | return true; 39 | } 40 | 41 | // If the identity isn't already set in cookie get the identity from the current user unless an identity is already pending push 42 | if (! $this->client->isIdentified() && $this->client->getPushCollection()->filter(fn($item) => $item[0] === 'identify')->isEmpty()) { 43 | return true; 44 | } 45 | 46 | return false; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | keyBy(fn($item, $key) => in_array(explode('.', $key)[0], KlaviyoClient::SERVER_PROFILE_ATTRIBUTES) ? $key : "properties.{$key}") 17 | ->undot() 18 | ->all(); 19 | } 20 | } 21 | --------------------------------------------------------------------------------