How to Sync Shopify Abandoned Checkouts into Klaviyo as Events

Catch every Shopify checkout that a shopper starts but never completes, and push a structured "Abandoned Checkout" event into Klaviyo with the cart value and line items so your recovery flow can fire automatically.

What This Integration Does

When a customer adds items to their cart, begins checkout, and then leaves without paying, Shopify records an abandoned checkout. That checkout is full of recoverable revenue, but only if your marketing platform hears about it in real time. This Spojit workflow listens for Shopify's checkout webhook, looks up (or creates) the matching Klaviyo profile, and records a custom event that carries the cart total and the specific products left behind. Klaviyo can then trigger a cart-recovery flow that emails the shopper a personalized "you left something behind" message, often with the exact items they were about to buy.

The workflow runs on a Webhook trigger: every time Shopify posts a checkouts/create or checkouts/update event, Spojit parses the JSON body, derives the customer email and a stable cart key, and calls Klaviyo to record a create-event against that profile. Each abandoned checkout produces one event in Klaviyo. Because Shopify fires the webhook again when a checkout is updated, the workflow uses a stable unique_id so re-sends do not pile up as duplicate events, and it only forwards checkouts that have an email and have not yet completed.

Prerequisites

  • A Shopify connection in Spojit, plus access to your Shopify admin to register a webhook for the checkouts/create (and optionally checkouts/update) topic.
  • A Klaviyo connection in Spojit (API key) with permission to read profiles and create events.
  • A Shopify signing connection in Spojit so the Webhook trigger can verify the request HMAC. See Setting Up a Webhook Connection.
  • A Klaviyo metric name decided in advance (this tutorial uses Abandoned Checkout) and, on the Klaviyo side, a flow that is triggered by that metric.

Step 1: Add a Webhook trigger and verify Shopify

Create a new workflow and set its Trigger node type to Webhook. In the trigger's signing scheme, choose Shopify and select the Shopify signing connection from the prerequisites so Spojit validates the HMAC header on every request. Copy the generated workflow URL. In your Shopify admin under Settings > Notifications > Webhooks, create a webhook for the checkouts/create topic (and a second one for checkouts/update if you want updates), point it at the copied URL, and choose the JSON format. The trigger output is the parsed checkout body, available downstream as {{ trigger }}, including fields such as {{ trigger.email }}, {{ trigger.token }}, {{ trigger.total_price }}, {{ trigger.completed_at }}, and {{ trigger.line_items }}.

Step 2: Skip checkouts that are completed or missing an email

Add a Condition node so the workflow only continues for genuinely abandoned carts. Shopify keeps sending checkout updates even after a checkout converts to an order, and guest carts can arrive without an email. Configure the condition to require that {{ trigger.email }} is present and that {{ trigger.completed_at }} is empty. Wire the true branch into the next step; leave the false branch unconnected so completed or anonymous checkouts end quietly. This keeps your Klaviyo event stream clean and avoids triggering the recovery flow for people who already paid.

Step 3: Find or create the Klaviyo profile

Add a Connector node in Direct mode, choose the Klaviyo connector, and pick the create-profile tool. Map the profile attributes from the checkout: set email to {{ trigger.email }}, and where Shopify provides them, set first_name to {{ trigger.customer.first_name }} and last_name to {{ trigger.customer.last_name }}. Klaviyo treats a profile create with an existing email as an upsert keyed on email, so this safely returns the existing profile instead of duplicating it. A sample input looks like this.

{
  "email": "{{ trigger.email }}",
  "first_name": "{{ trigger.customer.first_name }}",
  "last_name": "{{ trigger.customer.last_name }}"
}

Name the output variable profile so later steps can reference {{ profile }}. If you prefer to attach the event by email only, you can skip this step, but creating the profile first lets you carry name and other attributes into Klaviyo.

Step 4: Reshape the line items into event properties

Add a Transform node to build a tidy summary of the cart that Klaviyo can use inside templates. Pull the title, quantity, and price out of each entry in {{ trigger.line_items }} and produce a compact list plus a total item count. Aim for an output shaped like this so it slots straight into the event properties in the next step.

{
  "item_count": 3,
  "items": [
    { "title": "Wool Runner", "quantity": 1, "price": "98.00" },
    { "title": "Trail Sock 2-Pack", "quantity": 2, "price": "18.00" }
  ],
  "cart_value": "134.00",
  "currency": "USD",
  "recovery_url": "https://your-store.myshopify.com/...?key=..."
}

Source cart_value from {{ trigger.total_price }}, currency from {{ trigger.currency }}, and the recovery link from {{ trigger.abandoned_checkout_url }} when Shopify includes it. Name the output variable cart.

Step 5: Record the abandoned-checkout event in Klaviyo

Add another Connector node in Direct mode, choose the Klaviyo connector, and pick the create-event tool. The event attributes carry a metric object (with a name), a profile object (with an email or id), free-form properties, a value, and a unique_id. Set metric.name to Abandoned Checkout, identify the profile by {{ trigger.email }}, place the cart summary from Step 4 in properties, set value to the cart total for revenue reporting, and set unique_id to the Shopify checkout token so repeated sends collapse into one event.

{
  "metric": { "name": "Abandoned Checkout" },
  "profile": { "email": "{{ trigger.email }}" },
  "properties": {
    "item_count": "{{ cart.item_count }}",
    "items": "{{ cart.items }}",
    "cart_value": "{{ cart.cart_value }}",
    "currency": "{{ cart.currency }}",
    "recovery_url": "{{ cart.recovery_url }}",
    "checkout_token": "{{ trigger.token }}"
  },
  "value": "{{ trigger.total_price }}",
  "unique_id": "{{ trigger.token }}"
}

Once this event lands, your Klaviyo cart-recovery flow that is triggered by the Abandoned Checkout metric will fire, and its messages can reference the items, cart_value, and recovery_url properties for personalization.

Step 6: Return a fast acknowledgement

The Webhook trigger already returns 202 with an execution id as soon as Shopify posts, so the heavy lifting happens after the response. If you want Spojit to confirm receipt explicitly, you can add a Response node early in the flow returning a small body like { "received": true }. Keep the Klaviyo calls after the response so a slow Klaviyo request never holds the webhook open. If you would rather build the whole flow conversationally, ask Miraxa, the intelligent layer across your automation, something like "Add a Klaviyo Connector node in Direct mode that calls create-event with metric Abandoned Checkout and connect it after my Transform node," and refine the field mapping in the properties panel afterward.

Tips

  • Set unique_id to a stable value such as the Shopify checkout {{ trigger.token }}. Klaviyo deduplicates events on that id, so when Shopify re-sends the same checkout you update one event rather than spawning many.
  • Keep value as the numeric cart total. Klaviyo can sum it for "revenue at risk" reporting and to prioritize high-value recovery sends.
  • Put everything a template needs (items, currency, recovery link) inside properties so your flow emails can render the cart without a second lookup.
  • Register only the checkout topics you need. Subscribing to checkouts/update as well as checkouts/create gives fresher cart contents but increases webhook volume, so make sure Step 2's condition is doing its job.

Common Pitfalls

  • Forgetting the email check. Guest checkouts often arrive without {{ trigger.email }}; without the Condition node those runs fail at the Klaviyo step or create profiles you cannot reach.
  • Treating completed checkouts as abandoned. Shopify fires checkout webhooks even after conversion, so always require {{ trigger.completed_at }} to be empty before recording the event.
  • Missing or wrong signing scheme. If the Webhook trigger's signing connection is not set to Shopify, valid Shopify posts can be rejected. See Setting Up a Webhook Trigger if requests are not arriving.
  • Metric name drift. The metric.name you send must match the metric your Klaviyo flow is triggered by, character for character. A typo means the event lands but no flow fires.

Testing

Before turning this on for live traffic, validate on a single cart. In a test Shopify store, start a checkout with a real test email, add a couple of products, and abandon it without paying. Open the workflow's execution in Spojit and confirm the Condition passed, the create-profile call returned your profile, the Transform produced a clean cart, and the create-event call succeeded. Then check Klaviyo: the profile should show an Abandoned Checkout event with the cart value and item list in its properties. Re-trigger the same checkout to confirm the shared unique_id updates the existing event instead of creating a duplicate. Only once a single cart flows end to end should you point production webhooks at the workflow.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.