How to Fan Out a Customer 360 Lookup Across Shopify, Stripe, and Klaviyo in Parallel

Build a Spojit workflow that takes a customer email from a webhook, runs three concurrent branches to pull orders, payments, and marketing profile at the same time, then merges the results into one structured 360 record.

What This Integration Does

When a support agent, an internal tool, or another system needs the full picture of a customer, that data usually lives in three places at once: their order history in Shopify, their payment activity in Stripe, and their marketing profile in Klaviyo. Querying those one after another is slow and the request stalls on whichever system is slowest. This workflow fans the lookup out into three branches that run side by side, so the total wait is roughly the time of the single slowest call rather than the sum of all three. The result is one tidy JSON object you can return to the caller, store, or feed into a later step.

The run model is a synchronous Webhook trigger: an external system POSTs a JSON body containing the customer email, Spojit verifies it against a signing connection, then a Parallel node splits execution into a Shopify branch, a Stripe branch, and a Klaviyo branch. Each branch is a Connector node in Direct mode that reads from its system. After all three branches complete, a Transform node merges their outputs into a single 360 record and a Response node returns it to the caller. The workflow reads only: it creates no records and leaves no state behind, so it is safe to re-run for the same email as often as you like. Each run is independent, and Stripe and Klaviyo are keyed off the email so repeated calls return the same view.

Prerequisites

  • A Shopify connection (API key) with read access to orders and customers. See the Shopify connector documentation.
  • A Stripe connection (API key) with read access to customers and charges.
  • A Klaviyo connection (API key) with read access to profiles.
  • A Webhook signing connection so Spojit can verify the incoming request. Any scheme works (Spojit, Shopify, GitHub, Slack, or Custom). See Setting Up a Webhook Connection.
  • The same customer email must exist in each system you query, or that branch simply returns an empty list.

Step 1: Add a Webhook trigger that receives the email

Create a new workflow and set the Trigger to Webhook. Pick your signing connection so the request is verified by HMAC, then copy the generated workflow URL. The caller POSTs a small JSON body:

{
  "email": "jordan@example.com"
}

The parsed body is available downstream as {{ input }}, so the email is {{ input.email }}. Because this is a synchronous lookup, you will return the 360 record with a Response node in the last step rather than relying on the default 202 acknowledgement. For more on this trigger, see Setting Up a Webhook Trigger.

Step 2: Add a Parallel node to fan out three branches

Add a Parallel node directly after the trigger and give it three branches: shopify, stripe, and klaviyo. Each branch runs concurrently and has its own chain of nodes. The Parallel node does not finish until every branch finishes, and each branch's output is namespaced so the next step can read all three. Keep each branch focused on one system so a slow or failing branch is easy to spot. For a deeper look at fan-out, see Using Parallel Nodes.

Step 3: Branch A - pull Shopify orders

In the shopify branch, add a Connector node in Direct mode, choose the Shopify connection, and select the list-orders tool. Shopify uses its own search syntax in the query field, so filter by email like this:

email:{{ input.email }}

Set first to a sensible page size such as 10 to get the most recent orders. Name the output variable shopify_orders. If you want the most recent order at a glance later, the orders list is returned newest-first. If you also need profile fields like name or default address, add a second Direct mode call to list-customers with the same email filter, but for a lean 360 record the order list is usually enough.

Step 4: Branch B - pull Stripe payments

Stripe keys charges to a customer ID rather than an email, so this branch is two Direct mode Connector nodes on the Stripe connection. First call list-customers with the email field set to {{ input.email }} and save it as stripe_customer. Then call list-charges with the customer field set to the matched id, for example {{ stripe_customer.data[0].id }}, and save it as stripe_charges. Set limit to 10. If you track in-progress payments rather than settled charges, use list-payment-intents with the same customer field instead. Add a small Condition node before the second call if you want to skip the charges lookup when no Stripe customer matched the email.

Step 5: Branch C - pull the Klaviyo marketing profile

In the klaviyo branch, add a Connector node in Direct mode on the Klaviyo connection and select list-profiles. Klaviyo filters with its own expression syntax, so set the filter field to:

equals(email,"{{ input.email }}")

Save the output as klaviyo_profile. This returns the profile with its attributes (email, first and last name, properties) plus any list memberships Klaviyo includes. If you already store the Klaviyo profile id elsewhere and want the single richest record, you can instead use get-profile with that id, but list-profiles with an email filter is the right choice when the email is all you have.

Step 6: Merge the three branches into one 360 record

After the Parallel node, add a Transform node to assemble the merged object. Reference each branch's saved variable and shape them into one structure:

{
  "email": "{{ input.email }}",
  "orders": {{ shopify_orders }},
  "payments": {{ stripe_charges }},
  "marketing": {{ klaviyo_profile }}
}

Save the result as customer_360. If you would rather have Miraxa, the intelligent layer across your automation, derive a few summary fields (lifetime order count, total paid, last campaign engaged), switch the merge step to a Connector node in Agent mode with a Response Schema so the output is forced into the exact JSON shape you want. Direct mode plus a Transform node keeps it deterministic and free of AI cost; Agent mode adds reasoning at the cost of AI credits. See How to Choose Between Agent Mode and Direct Mode.

Step 7: Return the record with a Response node

Finish with a Response node so the synchronous webhook caller gets the merged record back in the same request. Set its body to {{ customer_360 }}. The caller now receives the full 360 view in one round trip. If you want to also archive every lookup, add a Connector node before the Response to insert {{ customer_360 }} into your database, or a Send Email node to mail it to a reviewer. See Using Response Nodes.

Tips

  • Keep page sizes small (first and limit around 10) so each branch stays fast; the whole point of the fan-out is low latency, and large pages slow the slowest branch and raise rate-limit risk.
  • Branches are independent, so a missing customer in one system does not fail the others. Decide per branch whether an empty result should map to an empty array or be flagged in the merged record.
  • Ask Miraxa to scaffold the shape for you, for example: "Add a Parallel node with three branches, each a Direct mode Connector node, for Shopify list-orders, Stripe list-customers, and Klaviyo list-profiles filtered by `{{ input.email }}`." Then fine-tune fields in the properties panel.
  • If you only ever need the latest order or the single newest charge, trim the merged record in the Transform step rather than returning whole lists, which keeps the Response payload small.

Common Pitfalls

  • Stripe charges are filtered by customer id, not email. Always run list-customers first and pass the resulting id into list-charges. Skipping that step returns unrelated charges or nothing.
  • Shopify and Klaviyo each have their own filter syntax. Use email:{{ input.email }} for Shopify list-orders and equals(email,"{{ input.email }}") for Klaviyo list-profiles. Plain key-value pairs will not filter correctly.
  • A read connection still needs the right scopes. If a branch returns an authorization error, recheck that the connection has read access to that resource. See Troubleshooting Connection Issues.
  • Webhook requests are verified by the signing connection, so a caller that does not sign the body correctly is rejected before any branch runs. Confirm the caller and the signing connection use the same scheme.

Testing

Before pointing live traffic at the workflow, test with a known customer who exists in all three systems. POST a single body with that email to the workflow URL, then open the run in execution history and inspect each branch in turn: confirm shopify_orders, stripe_charges, and klaviyo_profile each populated, and that the Parallel node waited for all three. Then test an email that exists in only one system to confirm the other branches return empty rather than failing the run, and that your Transform step still produces a valid customer_360. Once both cases look right, share the URL with the calling system.

Learn More

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