@spindlecommerce/plugin-klaviyo
v1.0.0
Published
Klaviyo for SpindleCommerce: customer profiles, list subscriptions for customers who accept marketing, and order events (Placed Order, Ordered Product, Fulfilled, Cancelled and Refunded Order).
Maintainers
Readme
Klaviyo, a SpindleCommerce plugin
Customer profiles, list subscriptions and order events in Klaviyo, for its flows, segments and reports:
- Profiles. Customers' names, phone numbers and locales, kept up to date as they register and change their details.
- Subscriptions. Customers who accept marketing join a list of your choice, subscribed to email marketing with their consent.
- Orders. Placed Order once an order's payment goes through, Ordered Product for each line, and Fulfilled, Canceled and Refunded Order as orders ship, are canceled and are refunded. These are Klaviyo's standard ecommerce events, which its order flows and revenue reports use.
The plugin calls Klaviyo's REST API, revision 2026-10-15, as the platform delivers its events. When Klaviyo is unreachable or answers with an error, the platform retries the delivery.
Setup
Add the plugin to your SpindleCommerce installation and deploy:
pnpm add @spindlecommerce/plugin-klaviyo// ecom.config.ts import { klaviyo } from "@spindlecommerce/plugin-klaviyo"; export default defineConfig({ plugins: [email(), stripe(), klaviyo()] });It needs plugin SDK 1.1, for customers' marketing consent and orders' coupon codes.
Create a private API key in Klaviyo, under Settings › API keys › Create Private API Key. Make it a custom key with:
- read access to Accounts, for Test connection;
- full access to Events, Lists, Profiles and Subscriptions.
Settings › Plugins › Klaviyo. Enter the key. On a server, the
KLAVIYO_PRIVATE_API_KEYenvironment variable can supply it instead. Test connection names the Klaviyo account the key belongs to.Choose a list for customers who accept marketing: copy its ID from the list's settings in Klaviyo into List ID. Without one, nobody is subscribed.
Map the metrics in Klaviyo. Under Analytics › Metrics › Edit mapped metrics, map:
- Placed Order as revenue;
- Ordered Product as ordered product;
- Canceled Order as cancelled sales;
- Refunded Order as refunded sales.
Klaviyo's predictive analytics, CLV and benchmark reports read these mappings.
Settings
- Private API key (secret,
KLAVIYO_PRIVATE_API_KEY). - List ID: customers who accept marketing join this list. Empty subscribes no one.
- Send Ordered Product events (on by default): one per order line, besides Placed Order.
Events sent
| Klaviyo event | Sent on | unique_id | value | Properties besides the order's |
|---|---|---|---|---|
| Placed Order | The first of order.payment_authorized and order.payment_captured, once per order | The order's ID | The order's total | — |
| Ordered Product | The same, one per line, when turned on | <order ID>:<line ID> | The line's total | Its own: OrderId, ProductID, SKU, ProductName, Quantity, ItemPrice, RowTotal |
| Fulfilled Order | order.fulfilled, one per shipment | <order ID>:<fulfillment number> | The order's total | FulfillmentNumber, Tracking (Carrier, Number, URL), FulfilledItems |
| Canceled Order | order.canceled, if Placed Order was sent | The order's ID | The order's total | Reason |
| Refunded Order | order.refunded, one per refund | The order's ID for the refund that completes it; <order ID>:<refund number> before that | The amount refunded | RefundNumber |
- The order's properties:
OrderId: the order number, such asUS-000042.Items:ProductID,SKU,ProductName,Quantity,ItemPriceandRowTotalfor each line.ItemNames, andDiscountCodes(the coupon codes entered).Subtotal,Discount,ShippingandTax.
- Amounts are in the order's currency (
value_currency), in major units:19.99, not1999. - The profile of each order event is the order's email. Its name comes from the billing address (the buyer's), else the shipping address.
- Times. Placed Order and Ordered Product carry the time the order was created. The others carry the time of the shipment, cancellation or refund.
- Once each. Klaviyo records one event per profile, metric and
unique_id, so a delivery the platform runs twice is not counted twice. - Why not
order.placed? It fires before payment, and an order whose payment is declined is canceled at once. So Klaviyo hears only of orders that were paid for, and Canceled Order follows only a Placed Order.
Customers (customer.created, customer.updated) are upserted through profile import, by email: first
and last name, phone number and locale (the one they last shopped in). Fields the store has no value
for are left as they are in Klaviyo.
Consent
- Only with consent. A customer is subscribed only when a list is set and they accept marketing
(
acceptsMarketing, given when registering or in their account). Everyone else gets a profile and their order events, and is never subscribed. - How. Klaviyo's bulk subscribe job records their consent to email marketing, given now, with
SpindleCommerceas its source. The list's opt-in setting applies: with double opt-in, Klaviyo asks them to confirm by email first. - Once per opt-in. Klaviyo's subscribe lifts unsubscribes, so customers are not subscribed again each time their account changes: someone who unsubscribed through a Klaviyo email stays unsubscribed. They are subscribed again if they withdraw consent in the store and give it again, or change their email address.
- Withdrawing consent isn't passed on. A customer who stops accepting marketing in the store stays subscribed in Klaviyo until they unsubscribe through its emails, or staff unsubscribe them there.
- Email only. Phone numbers go on profiles, but nobody is subscribed to SMS.
Limitations
- No backfill. Customers and orders from before the plugin was set up aren't sent. A customer is synced at their next change. Import history with Klaviyo's CSV import.
- Phone numbers are sent only in international format (
+44 20 7946 0000). The profile syncs without them when they are in another format, or Klaviyo refuses them (another profile has the number, say). - Profiles are matched by email. A customer who changes their email address gets a new profile. The old one keeps its history and subscription.
- Items carry no product URLs, images, categories or brands.
- Refunds and CLV. Klaviyo ties Refunded Order and Canceled Order to a Placed Order by
unique_idin CLV.- The refund that leaves an order fully refunded uses the order's ID, so the order leaves the customer's lifetime value.
- Partial refunds before it get their own IDs: they are recorded, but don't lower CLV.
- Canceled Order uses the order's ID, as Placed Order does.
- Event names. These follow Klaviyo's guide for platforms without a pre-built integration. It spells Canceled Order with one "l"; Klaviyo's Shopify integration sends "Cancelled Order". Map whichever you use as cancelled sales.
- API revision. Klaviyo deprecates a revision a year after its release and retires it a year later,
then answers as the next one. 2026-10-15 is stable until October 2027 and retired in October 2028.
Raise
REVISIONinsrc/klaviyo.tsbefore then, after checking Klaviyo's changelog.
Develop
pnpm install
pnpm test
pnpm buildThe tests run the plugin against a fake Klaviyo API. The plugin API it builds on is @spindlecommerce/plugin-sdk.
Release
Each version is staged on npm and goes live when a maintainer approves it.
- Raise
versioninpackage.jsonand insrc/index.ts. A test checks they match. - Commit, then tag and push: for example
git tag v1.0.1 && git push origin main v1.0.1. - The Release workflow tests, builds and stages the version, and creates the GitHub release.
- Approve it on npmjs.com, under Staged Packages, with 2FA.
The workflow publishes with npm's trusted publishing, so it needs no token. Before its first release, a maintainer links the package to the workflow:
npm trust github @spindlecommerce/plugin-klaviyo --repo spindlecommerce/plugin-klaviyo --file release.yml --allow-stage-publishRun it just before tagging: npm drops a link that isn't used within 48 hours.
License
MIT
