Download the PHP package nickdnk/klaviyo-php-sdk without Composer

On this page you can find all versions of the php package nickdnk/klaviyo-php-sdk. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package klaviyo-php-sdk

Klaviyo PHP SDK

CI Coverage Latest version Downloads GitHub License

This is a custom PHP client for the Klaviyo API.

Klaviyo publishes an official, generated package, klaviyo/api. This one differs from it in a few ways:

Things to know before choosing it:

PHP Versions support

Requires PHP 8.3 and is tested against 8.3, 8.4 and 8.5.

API revision

Klaviyo versions its API with dated revisions and keeps old revisions available for a long time. This package pins one:

Authentication

OAuth

withOAuth() takes three things:

The callback is the only point where the SDK calls back into your code. It receives the credentials the client holds and a TokenExchange that performs the token request against Klaviyo (it already knows your client id, secret and transport). It returns the credentials to continue with. The SDK never calls the token endpoint by itself, so the callback decides whether a refresh happens at all.

What happens on a 401:

  1. The client calls your refresh callback with the credentials it currently holds.
  2. The callback returns new OAuthCredentials, usually by calling $exchange() and persisting the result.
  3. The original request is retried once with the returned access token.
  4. If that retry is also a 401, you get a ClientException.

If $exchange() fails you get an OAuthException. Its isInvalidGrant() returns true when the refresh token was revoked or expired, meaning the user has to connect the account again. Anything the callback throws propagates out of the API call that triggered it.

Always store the whole OAuthCredentials object. The access token changes on every refresh, and the refresh token may rotate in the future.

You can also refresh ahead of time, for example before a long job. This runs the same callback:

Several processes sharing one connection

Klaviyo rotates the refresh token, so two processes refreshing the same connection at once leaves one of them with a dead pair. Because the callback owns the exchange, it can serialise refreshes and reuse what another process already stored. $lock and $store below are pseudocode for whatever your application uses (a Redis lock, a database row, ...):

Compare access tokens rather than calling isExpired(): a 401 can also mean the token was revoked before its expiry, and a stored pair that differs from the one the client holds is fresh either way.

Connecting an account

This is the authorization-code flow with PKCE:

Disconnecting an account

Call APIClient::revokeToken($clientId, $clientSecret, $credentials->refreshToken). It invalidates the refresh token and the access token. Then delete the credentials you stored.

Requests

Each API area is a property on the client, such as $client->profiles or $client->campaigns. All of them are declared on APIClient, so your IDE lists them.

The maximum page[size] differs per endpoint, and Klaviyo answers 400 when it is exceeded:

Queries and filters

Query collects the query parameters of a request. Filter builds Klaviyo's filter expressions and takes care of the quoting, so you never concatenate user input into a filter string yourself.

Filter operators and what they serialise to:

Call Expression
Filter::equals('email', '[email protected]') equals(email,"[email protected]")
Filter::any('status', ['queued', 'processing']) any(status,["queued","processing"])
Filter::greaterThan('created', $dateTime) greater-than(created,2026-01-01T00:00:00+00:00)
Filter::lessThan(...), lessOrEqual(...), greaterOrEqual(...) less-than(...), less-or-equal(...), greater-or-equal(...)
Filter::contains('name', 'vip'), startsWith(...), endsWith(...) contains(name,"vip"), starts-with(...), ends-with(...)
Filter::containsAny('tags', [...]), containsAll(...) contains-any(tags,[...]), contains-all(...)
Filter::has('phone_number') has(phone_number)
Filter::all($a, $b) a,b (Klaviyo's AND; there is no OR)

How values are written:

A Filter is Stringable. Query::filter() also accepts a raw string for expressions the builder does not cover.

Each endpoint accepts its own set of fields and operators, listed in Klaviyo's API reference. Anything else is a 400. Some examples:

Pagination

Klaviyo paginates with cursors. Every list() result carries a links object:

The easiest way through all pages is iterate(), available on every service that has list(). It is a generator that fetches the next page only when the current one is exhausted, so breaking out of the loop stops the requests:

To see each page with its links, use paginate() with the same list() call:

Relationship listings take a next: argument instead, so wrap them with paginate(). The callback gets null for the first page and links->next after that:

To page by hand, pass links->next back with next:. The URL already contains every query parameter of the original request, so the Query is not needed again:

If you keep the cursor somewhere, for example to resume a job later, use Query::cursor(). It accepts either the bare page[cursor] value or the full next URL and extracts the cursor from it, and it keeps the rest of the Query (filter, fields, page size) so the follow-up request matches the first one:

Relationships and include

getRelationship($name) returns a Relationship object:

Without include, the related resources carry only their id. With include, they arrive fully hydrated in the same place:

Two cases look similar but are not:

Clearing a value

Request bodies leave out properties that are null or an empty array. Some PATCH endpoints need an explicit null or [] to clear a field. Use Explicit for those:

Errors

All exceptions extend nickdnk\Klaviyo\Exceptions\BaseException.

Concurrency

Every service method accepts returnRequest: true. It then returns the prepared PSR-7 request instead of sending it. executePool() sends a batch of such requests and returns the results in the same order. A failed entry is an exception object in that position, not a thrown exception.

Whether the batch actually runs in parallel depends on the transport:

Building requests lazily

Every prepared request carries its own encoded body, and the pool only ever holds concurrency of them at a time. Yield the requests from a generator so they are built as the pool consumes them, instead of building all of them first.

The body has to be built inside the generator for this to help; a generator over ready-made requests saves nothing. executePoolLazy($items, $toRequest) is the same thing for shorter cases, calling $toRequest on each item just before that request is sent.

Two things to know when choosing concurrency:

Retries and rate limits

The client retries these on its own:

When Klaviyo sends a Retry-After header, the client waits exactly that long. Otherwise it backs off exponentially (2s × 2^(attempt−1), capped at 60 s, with ±50 % jitter). Ten attempts in total. Every other 4xx or 5xx is final.

Transports

With Guzzle installed, nothing needs configuring. Any other PSR-18 client can be wrapped in a Psr18Transport.

Every constructor and static OAuth call also accepts a transport explicitly; APIClient::setDefaultTransport() sets a process-wide fallback for the ones that don't get one.

Webhooks

A few things to know first:

What parseWebhookRequest() verifies:

Development

Contributing

Issues and pull requests are welcome, in particular for:

For a pull request:

License

MIT. See LICENSE.


All versions of klaviyo-php-sdk with dependencies

PHP Build Version
Package Version
Requires php Version ^8.3
psr/http-client Version ^1.0
psr/http-client-implementation Version ^1.0
psr/http-factory Version ^1.0
psr/http-factory-implementation Version ^1.0
psr/http-message Version ^1.1 || ^2.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package nickdnk/klaviyo-php-sdk contains the following files

Loading the files please wait ...