How to Sync Stripe Customers into NetSuite as Customer Records

When a customer is created or updated in Stripe, this Spojit workflow creates or upserts a matching customer record in NetSuite so your billing and ERP customer data stay aligned.

What This Integration Does

Finance and operations teams often hold two copies of the same customer: one in Stripe, where cards are charged and invoices are paid, and one in NetSuite, where revenue is recognised and statements are issued. When those two records drift apart you get duplicate accounts, mismatched email addresses, and reconciliation headaches at month end. This workflow keeps the NetSuite customer record in step with Stripe automatically, so every paying customer in Stripe has exactly one corresponding NetSuite record carrying the same contact details.

The workflow runs from a Webhook trigger that Stripe calls whenever a customer.created or customer.updated event fires. Spojit parses the event body, reads the Stripe customer the event refers to, then writes that customer into NetSuite using an upsert keyed on the Stripe customer id. Because the write is an upsert, the first event for a customer creates the NetSuite record and every later event updates the same record in place. Re-running the same event is safe: the external id never changes, so you get an update rather than a duplicate. The workflow leaves Stripe untouched and writes only to NetSuite.

Prerequisites

  • A Stripe connection added under Connections with an API key that can read customers (used by the get-customer tool).
  • A NetSuite connection with permission to read and write customer records (used by list-customers, create-customer, and upsert-record). Run verify-connection once to confirm the connection works.
  • A signing connection for the Webhook trigger. Add one under Connections using the Stripe signing scheme, then paste the matching signing secret from your Stripe webhook endpoint.
  • A NetSuite subsidiary internal id to assign new customers (required in OneWorld accounts). Have one value ready, for example 1.
  • An agreed key field. This tutorial stores the Stripe customer id (for example cus_123) as the NetSuite External ID so the two systems can be matched on every run.

Step 1: Receive the Stripe event with a Webhook trigger

Add a Trigger node and set its type to Webhook. Spojit gives you a unique URL; copy it into Stripe under Developers > Webhooks > Add endpoint and subscribe to the customer.created and customer.updated events. In the trigger, select your Stripe signing connection so Spojit verifies each request before running. The trigger output is the parsed event body, available as {{ input }}. The Stripe customer id you need lives at {{ input.data.object.id }} and the event name at {{ input.type }}.

Step 2: Read the full customer from Stripe

Add a Connector node in Direct mode, choose the Stripe connector, and select the get-customer tool. Map its customerId input to {{ input.data.object.id }}. Reading the customer fresh (rather than trusting the webhook payload) guarantees you have the current email, name, phone, and address even if Stripe batched several edits. Name the output variable stripeCustomer. Key fields you will reuse are {{ stripeCustomer.id }}, {{ stripeCustomer.email }}, and {{ stripeCustomer.name }}.

Step 3: Split the Stripe name into first and last name

NetSuite stores a person customer as firstName and lastName, but Stripe holds a single name string. Add a Transform node to derive the two parts from {{ stripeCustomer.name }}. Produce an object such as the one below, falling back to the email local part when no name is present:

{
  "companyName": "{{ stripeCustomer.name }}",
  "firstName": "{{ stripeCustomer.name }}",
  "lastName": "(via Stripe)",
  "email": "{{ stripeCustomer.email }}",
  "phone": "{{ stripeCustomer.phone }}"
}

Name the output mapped. If you prefer the split to be exact, ask Miraxa, the intelligent layer across your automation, to "split {{ stripeCustomer.name }} into firstName and lastName in this Transform node" and it will fill the mapping for you.

Step 4: Upsert the customer into NetSuite by external id

Add a Connector node in Direct mode, choose the NetSuite connector, and select the upsert-record tool. This tool creates the record on the first event and updates the same record on every later event, keyed by external id, so you never create duplicates. Set its inputs as follows:

  • recordType: customer
  • externalId: {{ stripeCustomer.id }} (the Stripe cus_... id becomes the NetSuite External ID)
  • body: the customer fields to write

A typical body for a person customer looks like this:

{
  "isPerson": true,
  "firstName": "{{ mapped.firstName }}",
  "lastName": "{{ mapped.lastName }}",
  "email": "{{ mapped.email }}",
  "phone": "{{ mapped.phone }}",
  "subsidiary": { "id": "1" }
}

For a business customer, set "isPerson": false and send "companyName": "{{ mapped.companyName }}" instead of the name parts. Name the output variable netsuiteCustomer; it carries the NetSuite internal id of the record that was written.

Step 5: Guard against duplicate email addresses

NetSuite rejects a customer whose email collides with a different existing record. Before the upsert, add a Connector node in Direct mode using the NetSuite connector and the list-customers tool to check whether the email is already taken by a record with a different external id. Pass a filter in the q field:

email IS "{{ stripeCustomer.email }}"

Add a Condition node after it. If list-customers returns an existing record whose external id is not {{ stripeCustomer.id }}, route the true branch to a Send Email node that flags the clash to your finance inbox for manual review; otherwise route the false branch on to the upsert in Step 4. This keeps automatic writes clean and surfaces genuine conflicts to a human.

Step 6: Notify on conflicts and confirm success

On the conflict branch, configure the Send Email node with a templated Subject like Stripe/NetSuite customer conflict: {{ stripeCustomer.email }} and a Body that includes {{ stripeCustomer.id }} and the conflicting NetSuite id. Remember that external recipients must be on the org allowlist under Settings > General > Email recipients. On the success path, you can add a second Send Email node, or skip notifications entirely and rely on the execution history. The NetSuite internal id of the synced record is available as {{ netsuiteCustomer.id }} for any later steps.

Tips

  • Subscribe to customer.created and customer.updated only. You do not need customer.deleted for a sync that mirrors active billing customers.
  • Always key the upsert on the Stripe customer id via externalId. It is stable for the life of the customer, unlike email, which people change.
  • Keep the Stripe read in Direct mode so the sync stays deterministic and free of AI cost. Reserve Agent mode for cases where you genuinely need judgement about which tool to call.
  • To backfill existing Stripe customers in one pass, build a separate scheduled workflow that pages through Stripe with list-customers and runs the same upsert logic over each one.

Common Pitfalls

  • Skipping signature verification. Without a Stripe signing connection on the Webhook trigger, anyone who finds the URL can write to NetSuite. Always select the signing connection.
  • Missing subsidiary. In NetSuite OneWorld accounts, create-customer and upsert-record fail without a valid subsidiary id. Confirm the correct internal id before going live.
  • Webhook replays creating duplicates. Stripe can resend events. Because the upsert is keyed on the stable external id, replays update the same record instead of duplicating it, so do not switch the key to email.
  • Person vs company mismatch. Sending firstName/lastName with "isPerson": false, or companyName with "isPerson": true, causes validation errors. Decide which type each Stripe customer maps to.

Testing

Before enabling the live endpoint, send a test event from Stripe (Developers > Webhooks > Send test webhook) for customer.created against a sandbox NetSuite connection. Open the run in execution history and confirm the trigger output shows the expected {{ input.data.object.id }}, that get-customer returned the right email, and that upsert-record created a NetSuite record whose External ID equals the Stripe id. Then edit that customer in Stripe to fire customer.updated and verify the same NetSuite record is updated rather than duplicated. Once both paths behave, point your production Stripe endpoint at the workflow URL.

Learn More

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