How to Centralize Customer Enrichment in a Shared Subworkflow
Build one reusable enrich-customer workflow that takes an email address, gathers the matching Stripe and Shopify details, and returns a single tidy profile that any other workflow in your workspace can call.
What This Integration Does
Most teams end up rebuilding the same customer lookup in workflow after workflow: one flow needs the Stripe customer id and subscription status, another needs the Shopify order history, and a third needs both. Copying those steps everywhere means every fix has to be made in many places, and the lookups slowly drift apart. In Spojit you can solve this once by building a single child workflow that does the enrichment, then calling it from a Subworkflow node wherever you need a customer profile. Change the child once and every parent picks up the change immediately.
This child workflow runs on a Manual trigger so it can accept an input object from its callers. A parent passes in an email address; the child looks the customer up in Stripe with the Stripe connector and in Shopify with the Shopify connector, optionally calls an external system over the HTTP connector, merges the pieces into one object, and returns that object as its final output. The child holds no state of its own: each call is independent and re-running a parent simply re-runs the lookup with the same input, so the pattern is safe to retry.
Prerequisites
- A Stripe connection (API key) added under Connections. See the Stripe connector article.
- A Shopify connection added under Connections. See the Shopify connector article.
- An understanding of how subworkflows pass data in and out. See Using Subworkflow Nodes.
- Optional: an HTTP connection or reachable internal endpoint if you also want to pull from a third system (for example a support tool or loyalty service).
- Customers should be identifiable by the same email across systems, since email is the key this workflow matches on.
Step 1: Create the enrich-customer child workflow with a Manual trigger
Create a new workflow named enrich-customer and open it in the designer. Add a Trigger node and set its type to Manual. A Manual trigger lets the workflow be started by a caller and makes the caller's payload available as the trigger output. Callers will pass a single field, email, so downstream nodes read it as {{ input.email }}. Keep this child small and focused: its only job is to turn an email into a complete profile.
Step 2: Look up the Stripe customer
Add a Connector node in Direct mode, choose the Stripe connector, and select the list-customers tool. Map its email input to the trigger value so Stripe returns only the matching record:
email: {{ input.email }}
limit: 1
Set the node's output variable to stripe. Because list-customers returns a list, the first entry is your customer; you will reference it as {{ stripe.data.0 }} in later steps. Direct mode is the right choice here: it is a single deterministic lookup with no AI cost.
Step 3: Pull recent payment activity from Stripe (optional but useful)
If you want billing context in the profile, add a second Connector node in Direct mode on the Stripe connector and select list-subscriptions (or list-charges for one-off payments). Filter by the customer id you just retrieved:
customer: {{ stripe.data.0.id }}
limit: 5
Set the output variable to stripeSubs. Guard against the case where no Stripe customer was found by wrapping this step in a Condition node that only continues when {{ stripe.data.0.id }} is present, so you never call a downstream tool with an empty id.
Step 4: Look up the Shopify customer and orders
Add a Connector node in Direct mode, choose the Shopify connector, and select list-customers. Shopify uses its own search syntax, so build the query from the email:
query: email:{{ input.email }}
first: 1
Set the output variable to shopify. To include order history, add another Shopify Connector node with the list-orders tool and a query that scopes orders to this person:
query: email:{{ input.email }}
first: 10
Set that output variable to shopifyOrders. Steps 2 to 4 are independent lookups, so you can wrap them in a Parallel node to run Stripe and Shopify concurrently and shave latency off every call.
Step 5: Optionally enrich from a third system over HTTP
If a profile also needs data that lives outside Stripe and Shopify, add a Connector node in Direct mode on the HTTP connector and select http-get. Point it at your internal lookup endpoint and pass the email as a parameter:
url: https://internal.example.com/customers?email={{ input.email }}
headers:
Authorization: Bearer {{ your token }}
Set the output variable to extra. Keep this step optional and tolerant: a missing record from a side system should not block the rest of the profile, so prefer to leave its fields empty rather than fail the run.
Step 6: Merge everything into one profile and return it
Add a Transform node to assemble the final object from the pieces you gathered. Produce a single, predictable shape that callers can rely on:
{
"email": "{{ input.email }}",
"stripeCustomerId": "{{ stripe.data.0.id }}",
"stripeName": "{{ stripe.data.0.name }}",
"subscriptionCount": "{{ stripeSubs.data.length }}",
"shopifyCustomerId": "{{ shopify.data.0.id }}",
"orderCount": "{{ shopifyOrders.data.length }}",
"found": true
}
Whatever the last node outputs becomes the child workflow's return value, which is exactly what flows back to the parent. Keep this output shape stable: parents bind to these field names, so renaming a key later means updating every caller.
Step 7: Call the child from a parent workflow
Open any parent workflow that needs a customer profile, add a Subworkflow node, and pick enrich-customer in the Workflow field. In the Input field, pass the email the parent already has, for example:
{ "email": "{{ trigger.customer_email }}" }
The parent pauses while the child runs through its own trigger and nodes, then resumes with the merged profile available as the node's output (for example {{ enrich.stripeCustomerId }} and {{ enrich.orderCount }}). Repeat this in every workflow that needs enrichment. Because all of them call the same child, a single edit to enrich-customer updates every parent at once. If you are unsure how to structure the input, ask Miraxa, the intelligent layer across your automation: it knows the workflow you are editing and can add and wire the Subworkflow node for you.
Tips
- Run the Stripe and Shopify lookups inside a Parallel node so the child returns faster; the merge step in Step 6 waits for both branches.
- Keep the child's return shape flat and documented in its description so callers always know which fields they can bind to.
- Set sensible
limitandfirstvalues on the list tools; you rarely need every record, and small pages keep each call fast. - Nest sparingly. A profile-enrichment child called by many parents is ideal, but avoid deep chains of subworkflows calling subworkflows.
Common Pitfalls
- Assuming a match always exists. A new email may have no Stripe or Shopify record, so guard id-dependent steps with a Condition node and include a
foundflag in the output. - Mixing up the search syntax. Stripe's
list-customerstakes a plainemailfilter, while Shopify'slist-customersexpects its query formemail:{{ input.email }}. - Renaming output keys after callers exist. Parents bind to specific field names, so changing the merged shape can silently break them; version the change and update callers together.
- Forgetting that list tools return arrays. Read the first match as
{{ stripe.data.0 }}rather than treating the whole list as the customer.
Testing
Validate the child on its own before wiring up any parents. Open enrich-customer, press Run, and supply a known email such as { "email": "test@example.com" } as the Manual trigger input. Check the execution log to confirm each lookup found the expected record and that the final Transform output has every field populated. Then test a deliberately unknown email to confirm the workflow returns found: false cleanly instead of failing. Once the child behaves, add one Subworkflow node to a single parent and run that parent end to end, checking that the child appears as its own entry in execution history and that its returned profile is visible in the parent's later steps.