PHP code example of nickdnk / klaviyo-php-sdk

1. Go to this page and download the library: Download nickdnk/klaviyo-php-sdk library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

nickdnk / klaviyo-php-sdk example snippets


use nickdnk\Klaviyo\APIClient;

$client = APIClient::withApiKey('pk_...');          // private API key
$client = APIClient::withAccessToken('eyJhbGci...'); // bearer token managed elsewhere, never refreshed

use nickdnk\Klaviyo\APIClient;
use nickdnk\Klaviyo\OAuthCredentials;
use nickdnk\Klaviyo\TokenExchange;

// Loaded from your own storage (database, secret store, ...).
$saved = [
    'access_token'  => 'eyJhbGciOiJFUzI1NiIsImtpZCI6IjAxYTA2Y...',
    'refresh_token' => 'eyJhbGciOiJFUzI1NiIsImtpZCI6IjAxYTA2Y...',
    'expires_at'    => 1788536447,
];

$client = APIClient::withOAuth(
    new OAuthCredentials($saved['access_token'], $saved['refresh_token'], $saved['expires_at']),
    clientId: $_ENV['KLAVIYO_CLIENT_ID'],
    clientSecret: $_ENV['KLAVIYO_CLIENT_SECRET'],
    refresh: function (OAuthCredentials $current, TokenExchange $exchange): OAuthCredentials {
        // Called on a 401, before the request that triggered it is retried.
        // See lock rotation example below for details.
        return $exchange($current);
    },
);

if ($client->getCredentials()->isExpired(graceSeconds: 120)) {
    $client->refreshCredentials();
}

refresh: function (OAuthCredentials $current, TokenExchange $exchange) use ($store, $lock, $accountId): OAuthCredentials {
    return $lock->synchronized("klaviyo-refresh:$accountId", function () use ($current, $exchange, $store, $accountId) {
        $stored = $store->load($accountId);
        if ($stored->accessToken !== $current->accessToken) {
            return $stored;                 // someone else refreshed while we waited for the lock
        }
        $fresh = $exchange($stored);
        $store->save($accountId, $fresh);
        return $fresh;
    });
},

use nickdnk\Klaviyo\APIClient;
use nickdnk\Klaviyo\OAuthScope;

$clientId     = $_ENV['KLAVIYO_CLIENT_ID'];
$clientSecret = $_ENV['KLAVIYO_CLIENT_SECRET'];
$redirectUri  = 'https://example.com/klaviyo/callback';

// 1. Redirect the user. Keep $verifier and $state in the session.
$verifier = APIClient::generateCodeVerifier();
$state    = bin2hex(random_bytes(16));
$url      = APIClient::getOAuthLink($clientId, $state, $verifier, [OAuthScope::profilesRead, OAuthScope::eventsWrite], $redirectUri);

// 2. On the callback: compare $_GET['state'] with the stored $state, then exchange the code.
$credentials = APIClient::exchangeCodeForToken($clientId, $clientSecret, $_GET['code'], $verifier, $redirectUri);
// persist $credentials as shown under OAuth above

use nickdnk\Klaviyo\Filter;
use nickdnk\Klaviyo\Query;
use nickdnk\Klaviyo\Resources\Request\CreateProfile;
use nickdnk\Klaviyo\Resources\Request\PatchProfile;

$new = new CreateProfile();
$new->email = '[email protected]';
$new->first_name = 'Jane';
$new->properties = ['plan' => 'pro'];
$profile = $client->profiles->create($new);        // Resources\Response\Profile

$patch = new PatchProfile($profile->id);
$patch->last_name = 'Doe';
$client->profiles->update($patch);

$query = (new Query())
    ->filter(Filter::all(
        Filter::equals('email', '[email protected]'),
        Filter::greaterThan('created', new DateTimeImmutable('-30 days')),
    ))
    ->fields('profile', 'email', 'first_name')
    ->sort('created', descending: true)
    ->pageSize(50);

$page = $client->profiles->list($query);            // ['data' => Profile[], 'links' => ?PaginationLinks]

foreach ($client->profiles->iterate($query) as $p) {            // all pages, fetched as you go
    // ...
}

use nickdnk\Klaviyo\Filter;
use nickdnk\Klaviyo\Query;

$query = (new Query())
    ->filter(Filter::equals('email', '[email protected]'))   // filter=equals(email,"[email protected]")
    ->fields('profile', 'email', 'created')                 // fields[profile]=email,created
    ->additionalFields('profile', 'subscriptions')          // additional-fields[profile]=subscriptions
    ->

use nickdnk\Klaviyo\Filter;
use nickdnk\Klaviyo\Query;

$query = (new Query())->filter(Filter::equals('email', '[email protected]'))->pageSize(50);

foreach ($client->profiles->iterate($query) as $profile) {
    echo $profile->email, PHP_EOL;
}

$all = iterator_to_array($client->profiles->iterate($query));   // everything in one array; fine for small sets

$paginator = $client->paginate(fn(?string $next) => $client->profiles->list($query, $next));
foreach ($paginator->pages() as $page) { /* ['data' => Profile[], 'links' => ?PaginationLinks] */ }
$all = $paginator->all();

$paginator = $client->paginate(fn(?string $next) => $client->lists->profiles($listId, $query, next: $next));
foreach ($paginator->items() as $profile) {
    echo $profile->email, PHP_EOL;
}

$page = $client->profiles->list($query);
while ($page['links']?->next !== null) {
    $page = $client->profiles->list(next: $page['links']->next);
}

$cursor = $page['links']->next;                                      // store this
$page   = $client->profiles->list((new Query())->filter(Filter::equals('email', '[email protected]'))->pageSize(50)->cursor($cursor));

use nickdnk\Klaviyo\Filter;
use nickdnk\Klaviyo\Query;

$tag = $client->tags->get('UXxKzq', (new Query())->er(Filter::equals('messages.channel', 'email'))->

use nickdnk\Klaviyo\Resources\Request\UpdateTrackingSetting;
use nickdnk\Klaviyo\Resources\Shared\Explicit;

$accountId = $client->accounts->list()['data'][0]->id;   // tracking settings are keyed by account id

$update = new UpdateTrackingSetting($accountId);
$update->utm_term = Explicit::null();
$update->custom_parameters = Explicit::emptyList();
$client->trackingSettings->update($update);

use nickdnk\Klaviyo\Exceptions\ClientException;
use nickdnk\Klaviyo\Resources\Request\CreateProfile;

$profile = new CreateProfile();
$profile->email = '[email protected]';

try {
    $client->profiles->create($profile);
} catch (ClientException $e) {
    $duplicateId = $e->getHttpStatus() === 409 ? $e->getFirstError()?->meta['duplicate_profile_id'] : null;
    foreach ($e->getErrors() as $error) {
        echo $error->pointer, ': ', $error->detail, PHP_EOL;
    }
}

use nickdnk\Klaviyo\APIClient;
use nickdnk\Klaviyo\Resources\Request\PatchProfile;

$profileIds = ['01HZX4M2K9Q8R7T6V5W4X3Y2Z1', '01HZX4M2K9Q8R7T6V5W4X3Y2Z2'];

$requests = array_map(function (string $id) use ($client) {
    $patch = new PatchProfile($id);
    $patch->properties = ['synced_at' => date(DATE_ATOM)];
    return $client->profiles->update($patch, returnRequest: true);
}, $profileIds);

$results = $client->executePool($requests, concurrency: 5);   // Profile[] in input order, exceptions in place of failures
APIClient::assertNoExceptions($results);

use nickdnk\Klaviyo\Resources\Request\ImportProfile;
use nickdnk\Klaviyo\Resources\Request\BulkImportJob;

/** @var iterable<array{email: string, first_name: string}> $rows  a database cursor, a CSV reader, anything */
$rows = readRowsFromSomewhere();
$listId = 'YafG4m';

$batches = (function () use ($client, $rows, $listId): Generator {

    $chunk = [];

    foreach ($rows as $row) {

        $profile = new ImportProfile();
        $profile->email = $row['email'];
        $profile->first_name = $row['first_name'];
        $chunk[] = $profile;

        if (count($chunk) === 10000) {
            // Yielded, not collected: this request exists only while the pool holds it.
            yield $client->profiles->bulkImport(new BulkImportJob($chunk, [$listId]), returnRequest: true);
            $chunk = [];
        }

    }

    if ($chunk) {
        yield $client->profiles->bulkImport(new BulkImportJob($chunk, [$listId]), returnRequest: true);
    }

})();

$results = $client->executePool($batches, concurrency: 3);
APIClient::assertNoExceptions($results);

$results = $client->executePoolLazy(
    $profileIds,
    function (string $id) use ($client) {
        $patch = new PatchProfile($id);
        $patch->properties = ['synced_at' => date(DATE_ATOM)];
        return $client->profiles->update($patch, returnRequest: true);
    },
    concurrency: 5
);

use nickdnk\Klaviyo\APIClient;
use nickdnk\Klaviyo\Http\RetryPolicy;

$client = APIClient::withApiKey('pk_...', retry: new RetryPolicy(maxAttempts: 5, baseDelaySeconds: 1.0, maxDelaySeconds: 20.0));

$limits = $client->getLastRateLimit();          // RateLimit-Limit / -Remaining / -Reset headers of the last response
if ($limits?->isNearlyExhausted(5)) {
    sleep($limits->resetSeconds ?? 1);
}
$limits?->burstLimit();                          // e.g. 75 for /api/profiles, 1 for /api/accounts
$limits?->windows;                               // e.g. [1 => 75, 60 => 750]

use nickdnk\Klaviyo\APIClient;
use nickdnk\Klaviyo\Http\Psr18Transport;
use Symfony\Component\HttpClient\Psr18Client;   // symfony/http-client, as one example of a PSR-18 client

$http = new Psr18Client();   // also a PSR-17 factory; pass factories explicitly when discovery should not decide
$client = APIClient::withApiKey('pk_...', Psr18Transport::create($http, requestFactory: $http, streamFactory: $http));

use nickdnk\Klaviyo\APIClient;
use nickdnk\Klaviyo\Resources\Request\CreateWebhook;
use nickdnk\Klaviyo\Resources\Shared\WebhookTopic;
use Psr\Http\Message\ServerRequestInterface;

$secret = bin2hex(random_bytes(32));   // store it; it verifies every delivery
$client->webhooks->create(new CreateWebhook(
    'orders', 'https://example.com/klaviyo/webhook', $secret,
    [WebhookTopic::OPENED_EMAIL, 'event:api.viewed_product'],
));

// In the endpoint handler, with the incoming PSR-7 request:
function handleKlaviyoWebhook(ServerRequestInterface $request, string $secret): int
{
    $webhook = APIClient::parseWebhookRequest($request, $secret);
    if ($webhook === null) {
        return 400;   // bad signature, stale or malformed
    }
    foreach ($webhook->events as ['topic' => $topic, 'payload' => $event]) {
        if ($topic->is(WebhookTopic::OPENED_EMAIL)) { /* $event is a hydrated Event */ }
    }
    return 200;
}