LoopFour

Customer Onboarding

Automate new customer setup across multiple systems

Customer Onboarding Tutorial

Learn how to automate new customer onboarding across multiple systems when a Stripe subscription is created, ensuring consistent setup and immediate engagement.

The Problem

When a new customer signs up, teams typically:

  1. Manually create the customer in the accounting system (QuickBooks/NetSuite)
  2. Add the contact to the CRM (HubSpot/Salesforce)
  3. Send a welcome email with setup instructions
  4. Notify the customer success team in Slack
  5. Create onboarding tasks or projects
  6. Update spreadsheets or dashboards

This manual process causes:

  • Delayed onboarding - Hours or days before customer is fully set up
  • Inconsistent data - Customer info varies across systems
  • Missed steps - Someone forgets to update a system
  • Poor first impression - Customer waits for manual actions
  • Team bottlenecks - One person becomes the onboarding gatekeeper

The Solution

Build a workflow that automatically:

  1. Triggers when a Stripe subscription is created
  2. Creates the customer in QuickBooks for invoicing
  3. Creates or updates the contact in HubSpot as a customer
  4. Sends a personalized welcome email
  5. Notifies the customer success team in Slack
  6. (Optional) Creates an onboarding project or task

What You'll Build

Stripe Subscription Created


    ┌───────────────────┐
    │ Get Stripe        │
    │ Customer Details  │
    └─────────┬─────────┘

        ┌─────┴─────┐
        ▼           ▼
┌───────────┐ ┌───────────┐
│ Create    │ │ Create/   │
│ QuickBooks│ │ Update    │
│ Customer  │ │ HubSpot   │
└─────┬─────┘ └─────┬─────┘
      └──────┬──────┘


    ┌───────────────────┐
    │ Send Welcome      │
    │ Email             │
    └─────────┬─────────┘


    ┌───────────────────┐
    │ Notify Team       │
    │ in Slack          │
    └───────────────────┘

Prerequisites

Required Connections

ProviderConnection TypePurpose
StripeOAuth via NangoReceive webhooks, get customer data
QuickBooksOAuth via NangoCreate accounting customers
HubSpotOAuth via NangoManage CRM contacts
SlackOAuth via NangoTeam notifications
Email ProviderOAuth or API KeyWelcome emails

Stripe Setup

  1. Configure Webhook in Stripe Dashboard:

    • Developers → Webhooks → Add endpoint
    • URL: https://your-domain.com/webhooks/stripe/{companyId}
    • Events: customer.subscription.created
  2. Note your webhook signing secret for verification

QuickBooks Setup

Ensure you have:

  • An active QuickBooks Online account
  • OAuth connection configured in Nango

HubSpot Setup

  1. Create Custom Properties (recommended):
    • stripe_customer_id (Single-line text)
    • stripe_subscription_id (Single-line text)
    • subscription_plan (Dropdown)
    • mrr (Number - currency)

Step-by-Step Implementation

Step 1: Create the Workflow

Start with the Stripe webhook trigger:

{
  "name": "Customer Onboarding",
  "description": "Automate new customer setup when Stripe subscription is created",
  "trigger": {
    "type": "webhook",
    "provider": "stripe",
    "events": ["customer.subscription.created"]
  },
  "steps": []
}

Step 2: Extract Customer Information

Parse the subscription data:

{
  "id": "extract-customer-data",
  "type": "transform",
  "name": "Extract Customer Data",
  "config": {
    "operation": "map",
    "data": {
      "customerId": "{{input.data.object.customer}}",
      "subscriptionId": "{{input.data.object.id}}",
      "planName": "{{input.data.object.items.data.[0].plan.nickname}}",
      "planAmount": "{{input.data.object.items.data.[0].plan.amount}}",
      "interval": "{{input.data.object.items.data.[0].plan.interval}}",
      "status": "{{input.data.object.status}}"
    }
  }
}

Step 3: Get Full Customer Details from Stripe

Retrieve the complete customer profile:

{
  "id": "get-stripe-customer",
  "type": "action",
  "name": "Get Stripe Customer Details",
  "config": {
    "connector": "stripe",
    "action": "getCustomer",
    "customerId": "{{steps.extract-customer-data.output.customerId}}"
  }
}

Output includes:

  • name - Customer name
  • email - Email address
  • phone - Phone number (if provided)
  • address - Billing address
  • metadata - Custom fields

Step 4: Create QuickBooks Customer

Create the customer in your accounting system:

{
  "id": "create-qb-customer",
  "type": "action",
  "name": "Create QuickBooks Customer",
  "config": {
    "connector": "quickbooks",
    "action": "createCustomer",
    "DisplayName": "{{steps.get-stripe-customer.output.name}}",
    "CompanyName": "{{steps.get-stripe-customer.output.metadata.company_name}}",
    "PrimaryEmailAddr": {
      "Address": "{{steps.get-stripe-customer.output.email}}"
    },
    "PrimaryPhone": {
      "FreeFormNumber": "{{steps.get-stripe-customer.output.phone}}"
    },
    "BillAddr": {
      "Line1": "{{steps.get-stripe-customer.output.address.line1}}",
      "City": "{{steps.get-stripe-customer.output.address.city}}",
      "CountrySubDivisionCode": "{{steps.get-stripe-customer.output.address.state}}",
      "PostalCode": "{{steps.get-stripe-customer.output.address.postal_code}}",
      "Country": "{{steps.get-stripe-customer.output.address.country}}"
    },
    "Notes": "Stripe Customer ID: {{steps.get-stripe-customer.output.id}}\nSubscription: {{steps.extract-customer-data.output.planName}}"
  },
  "onError": {
    "action": "continue",
    "fallback": { "Id": null }
  }
}

Step 5: Create or Update HubSpot Contact

Add the customer to your CRM:

{
  "id": "upsert-hubspot-contact",
  "type": "action",
  "name": "Create/Update HubSpot Contact",
  "config": {
    "connector": "hubspot",
    "action": "createContact",
    "email": "{{steps.get-stripe-customer.output.email}}",
    "properties": {
      "firstname": "{{steps.get-stripe-customer.output.name | split: ' ' | first}}",
      "lastname": "{{steps.get-stripe-customer.output.name | split: ' ' | last}}",
      "phone": "{{steps.get-stripe-customer.output.phone}}",
      "company": "{{steps.get-stripe-customer.output.metadata.company_name}}",
      "lifecyclestage": "customer",
      "stripe_customer_id": "{{steps.get-stripe-customer.output.id}}",
      "stripe_subscription_id": "{{steps.extract-customer-data.output.subscriptionId}}",
      "subscription_plan": "{{steps.extract-customer-data.output.planName}}",
      "mrr": "{{steps.extract-customer-data.output.planAmount | divided_by: 100}}"
    }
  },
  "onError": {
    "action": "continue",
    "fallback": { "id": null }
  }
}

Key properties:

  • lifecyclestage: customer - Marks them as a paying customer
  • Custom Stripe fields for tracking

Step 6: Send Welcome Email

Send a personalized welcome email:

{
  "id": "send-welcome-email",
  "type": "email",
  "name": "Send Welcome Email",
  "config": {
    "action": "send",
    "provider": "sendgrid",
    "to": "{{steps.get-stripe-customer.output.email}}",
    "from": "onboarding@yourcompany.com",
    "subject": "Welcome to {{steps.extract-customer-data.output.planName}}!",
    "body": {
      "html": "<h1>Welcome, {{steps.get-stripe-customer.output.name}}!</h1><p>Thank you for subscribing to <strong>{{steps.extract-customer-data.output.planName}}</strong>. We're excited to have you on board!</p><h2>Getting Started</h2><p>Here are your next steps:</p><ol><li><a href=\"https://app.yourcompany.com/onboarding\">Complete your profile setup</a></li><li><a href=\"https://docs.yourcompany.com/quickstart\">Read our quickstart guide</a></li><li><a href=\"https://yourcompany.com/book-call\">Book an onboarding call</a> with our team</li></ol><p>If you have any questions, reply to this email or reach out to <a href=\"mailto:support@yourcompany.com\">support@yourcompany.com</a>.</p><p>Welcome aboard!</p><p>The {{steps.extract-customer-data.output.planName}} Team</p>"
    },
    "template_variables": {
      "customer_name": "{{steps.get-stripe-customer.output.name}}",
      "plan_name": "{{steps.extract-customer-data.output.planName}}",
      "app_url": "https://app.yourcompany.com"
    }
  }
}

Step 7: Notify Customer Success Team

Alert the team about the new customer:

{
  "id": "notify-cs-team",
  "type": "slack",
  "name": "Notify Customer Success",
  "config": {
    "action": "sendMessage",
    "channel": "#new-customers",
    "blocks": [
      {
        "type": "header",
        "text": {
          "type": "plain_text",
          "text": "New Customer Signed Up!"
        }
      },
      {
        "type": "section",
        "fields": [
          {
            "type": "mrkdwn",
            "text": "*Customer:*\n{{steps.get-stripe-customer.output.name}}"
          },
          {
            "type": "mrkdwn",
            "text": "*Plan:*\n{{steps.extract-customer-data.output.planName}}"
          },
          {
            "type": "mrkdwn",
            "text": "*Email:*\n{{steps.get-stripe-customer.output.email}}"
          },
          {
            "type": "mrkdwn",
            "text": "*MRR:*\n${{steps.extract-customer-data.output.planAmount | divided_by: 100}}/{{steps.extract-customer-data.output.interval}}"
          }
        ]
      },
      {
        "type": "context",
        "elements": [
          {
            "type": "mrkdwn",
            "text": "QuickBooks: {{#if steps.create-qb-customer.output.Id}}Created{{else}}Skipped{{/if}} | HubSpot: {{#if steps.upsert-hubspot-contact.output.id}}Created{{else}}Skipped{{/if}}"
          }
        ]
      },
      {
        "type": "actions",
        "elements": [
          {
            "type": "button",
            "text": {
              "type": "plain_text",
              "text": "View in Stripe"
            },
            "url": "https://dashboard.stripe.com/customers/{{steps.get-stripe-customer.output.id}}"
          },
          {
            "type": "button",
            "text": {
              "type": "plain_text",
              "text": "View in HubSpot"
            },
            "url": "https://app.hubspot.com/contacts/YOUR_PORTAL_ID/contact/{{steps.upsert-hubspot-contact.output.id}}"
          }
        ]
      }
    ]
  }
}

Complete Workflow

Here's the complete workflow JSON:

{
  "name": "Customer Onboarding",
  "description": "Automate new customer setup when Stripe subscription is created",
  "trigger": {
    "type": "webhook",
    "provider": "stripe",
    "events": ["customer.subscription.created"]
  },
  "steps": [
    {
      "id": "extract-customer-data",
      "type": "transform",
      "name": "Extract Customer Data",
      "config": {
        "operation": "map",
        "data": {
          "customerId": "{{input.data.object.customer}}",
          "subscriptionId": "{{input.data.object.id}}",
          "planName": "{{input.data.object.items.data.[0].plan.nickname}}",
          "planAmount": "{{input.data.object.items.data.[0].plan.amount}}",
          "interval": "{{input.data.object.items.data.[0].plan.interval}}"
        }
      }
    },
    {
      "id": "get-stripe-customer",
      "type": "action",
      "name": "Get Stripe Customer Details",
      "config": {
        "connector": "stripe",
        "action": "getCustomer",
        "customerId": "{{steps.extract-customer-data.output.customerId}}"
      }
    },
    {
      "id": "create-qb-customer",
      "type": "action",
      "name": "Create QuickBooks Customer",
      "config": {
        "connector": "quickbooks",
        "action": "createCustomer",
        "DisplayName": "{{steps.get-stripe-customer.output.name}}",
        "PrimaryEmailAddr": {
          "Address": "{{steps.get-stripe-customer.output.email}}"
        },
        "Notes": "Stripe ID: {{steps.get-stripe-customer.output.id}}"
      },
      "onError": {
        "action": "continue",
        "fallback": { "Id": null }
      }
    },
    {
      "id": "upsert-hubspot-contact",
      "type": "action",
      "name": "Create/Update HubSpot Contact",
      "config": {
        "connector": "hubspot",
        "action": "createContact",
        "email": "{{steps.get-stripe-customer.output.email}}",
        "properties": {
          "firstname": "{{steps.get-stripe-customer.output.name | split: ' ' | first}}",
          "lastname": "{{steps.get-stripe-customer.output.name | split: ' ' | last}}",
          "lifecyclestage": "customer",
          "stripe_customer_id": "{{steps.get-stripe-customer.output.id}}",
          "subscription_plan": "{{steps.extract-customer-data.output.planName}}"
        }
      },
      "onError": {
        "action": "continue",
        "fallback": { "id": null }
      }
    },
    {
      "id": "send-welcome-email",
      "type": "email",
      "name": "Send Welcome Email",
      "config": {
        "action": "send",
        "provider": "sendgrid",
        "to": "{{steps.get-stripe-customer.output.email}}",
        "from": "onboarding@yourcompany.com",
        "subject": "Welcome to {{steps.extract-customer-data.output.planName}}!",
        "body": {
          "html": "<h1>Welcome!</h1><p>Thank you for subscribing. We're excited to have you on board!</p>"
        }
      }
    },
    {
      "id": "notify-cs-team",
      "type": "slack",
      "name": "Notify Customer Success",
      "config": {
        "action": "sendMessage",
        "channel": "#new-customers",
        "blocks": [
          {
            "type": "header",
            "text": { "type": "plain_text", "text": "New Customer Signed Up!" }
          },
          {
            "type": "section",
            "fields": [
              { "type": "mrkdwn", "text": "*Customer:*\n{{steps.get-stripe-customer.output.name}}" },
              { "type": "mrkdwn", "text": "*Plan:*\n{{steps.extract-customer-data.output.planName}}" }
            ]
          },
          {
            "type": "actions",
            "elements": [
              {
                "type": "button",
                "text": { "type": "plain_text", "text": "View in Stripe" },
                "url": "https://dashboard.stripe.com/customers/{{steps.get-stripe-customer.output.id}}"
              }
            ]
          }
        ]
      }
    }
  ]
}

Testing

1. Create Test Workflow

curl -X POST http://localhost:3000/api/v1/workflows \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d @workflow.json

2. Activate Workflow

curl -X POST http://localhost:3000/api/v1/workflows/{workflowId}/activate \
  -H "x-api-key: YOUR_API_KEY"

3. Test with Simulated Webhook

curl -X POST http://localhost:3000/webhooks/stripe/{companyId} \
  -H "Content-Type: application/json" \
  -H "stripe-signature: test" \
  -d '{
    "type": "customer.subscription.created",
    "data": {
      "object": {
        "id": "sub_1234567890",
        "customer": "cus_1234567890",
        "status": "active",
        "items": {
          "data": [{
            "plan": {
              "nickname": "Pro Plan",
              "amount": 9900,
              "interval": "month"
            }
          }]
        }
      }
    }
  }'

4. Verify Results

SystemWhat to Verify
QuickBooksNew customer created with Stripe ID in notes
HubSpotContact created with lifecycle stage = customer
EmailWelcome email received
SlackNotification in #new-customers

5. End-to-End Test

  1. Create a test product and price in Stripe
  2. Use Stripe test mode to create a subscription
  3. Watch the workflow execute automatically
  4. Verify all systems updated

Error Handling

Graceful Degradation

The workflow uses onError: { action: "continue" } for non-critical steps so the workflow continues even if one system fails:

{
  "id": "create-qb-customer",
  "config": { ... },
  "onError": {
    "action": "continue",
    "fallback": { "Id": null }
  }
}

Notify on Partial Failure

Add a check at the end:

{
  "id": "check-all-succeeded",
  "type": "condition",
  "config": {
    "if": "{{steps.create-qb-customer.output.Id && steps.upsert-hubspot-contact.output.id}}",
    "then": "end",
    "else": "notify-partial-failure"
  }
},
{
  "id": "notify-partial-failure",
  "type": "slack",
  "config": {
    "action": "sendMessage",
    "channel": "#ops-alerts",
    "text": ":warning: Customer onboarding partially failed for {{steps.get-stripe-customer.output.email}}. Check QuickBooks: {{#unless steps.create-qb-customer.output.Id}}FAILED{{/unless}}, HubSpot: {{#unless steps.upsert-hubspot-contact.output.id}}FAILED{{/unless}}"
  }
}

Handle Duplicate Customers

Check if customer already exists:

{
  "id": "check-qb-customer-exists",
  "type": "action",
  "config": {
    "connector": "quickbooks",
    "action": "findCustomerByEmail",
    "email": "{{steps.get-stripe-customer.output.email}}"
  }
},
{
  "id": "decide-create-or-skip",
  "type": "condition",
  "config": {
    "if": "{{steps.check-qb-customer-exists.output.QueryResponse.Customer.length > 0}}",
    "then": "skip-qb-creation",
    "else": "create-qb-customer"
  }
}

Variations and Extensions

Plan-Specific Onboarding

Different onboarding flows for different plans:

{
  "id": "route-by-plan",
  "type": "condition",
  "config": {
    "if": "{{steps.extract-customer-data.output.planName === 'Enterprise'}}",
    "then": "enterprise-onboarding",
    "else": {
      "if": "{{steps.extract-customer-data.output.planName === 'Pro'}}",
      "then": "pro-onboarding",
      "else": "basic-onboarding"
    }
  }
}

Create NetSuite Customer Instead

For enterprise companies using NetSuite:

{
  "id": "create-netsuite-customer",
  "type": "action",
  "config": {
    "connector": "netsuite",
    "action": "createCustomer",
    "companyName": "{{steps.get-stripe-customer.output.metadata.company_name}}",
    "email": "{{steps.get-stripe-customer.output.email}}",
    "subsidiary": { "id": "1" },
    "customFields": {
      "custentity_stripe_id": "{{steps.get-stripe-customer.output.id}}"
    }
  }
}

Scheduled Follow-up Email

Send a check-in email 7 days after signup:

{
  "id": "schedule-followup",
  "type": "email",
  "config": {
    "action": "schedule",
    "provider": "resend",
    "to": "{{steps.get-stripe-customer.output.email}}",
    "subject": "How's it going with {{steps.extract-customer-data.output.planName}}?",
    "body": {
      "html": "<p>Hi {{steps.get-stripe-customer.output.name}},</p><p>You've been using {{steps.extract-customer-data.output.planName}} for a week now. How's it going?</p><p>If you have any questions or need help, just reply to this email!</p>"
    },
    "send_at": "{{now | dateAdd: 7, 'days' | date: '%Y-%m-%dT%H:%M:%SZ'}}"
  }
}

Create Salesforce Account and Contact

For Salesforce users:

{
  "id": "create-sf-account",
  "type": "action",
  "config": {
    "connector": "salesforce",
    "action": "createOpportunity",
    "Name": "{{steps.get-stripe-customer.output.metadata.company_name}} - New Subscription",
    "StageName": "Closed Won",
    "CloseDate": "{{now | date: '%Y-%m-%d'}}",
    "Amount": "{{steps.extract-customer-data.output.planAmount | divided_by: 100}}"
  }
},
{
  "id": "create-sf-contact",
  "type": "action",
  "config": {
    "connector": "salesforce",
    "action": "createContact",
    "FirstName": "{{steps.get-stripe-customer.output.name | split: ' ' | first}}",
    "LastName": "{{steps.get-stripe-customer.output.name | split: ' ' | last}}",
    "Email": "{{steps.get-stripe-customer.output.email}}",
    "Stripe_Customer_ID__c": "{{steps.get-stripe-customer.output.id}}"
  }
}

Assign CSM Based on Plan

Route high-value customers to senior CSMs:

{
  "id": "determine-csm",
  "type": "transform",
  "config": {
    "operation": "map",
    "data": {
      "csmEmail": "{{#if (gt steps.extract-customer-data.output.planAmount 50000)}}senior-csm@company.com{{else if (gt steps.extract-customer-data.output.planAmount 10000)}}csm@company.com{{else}}onboarding@company.com{{/if}}",
      "csmSlackId": "{{#if (gt steps.extract-customer-data.output.planAmount 50000)}}U12345{{else}}U67890{{/if}}"
    }
  }
},
{
  "id": "notify-assigned-csm",
  "type": "slack",
  "config": {
    "action": "sendMessage",
    "channel": "@{{steps.determine-csm.output.csmSlackId}}",
    "text": "You've been assigned a new customer: {{steps.get-stripe-customer.output.name}} ({{steps.extract-customer-data.output.planName}})"
  }
}

Trial Conversion Variant

Handle trial-to-paid conversion differently:

{
  "trigger": {
    "type": "webhook",
    "provider": "stripe",
    "events": ["customer.subscription.updated"],
    "filter": {
      "data.object.status": { "$eq": "active" },
      "data.previous_attributes.status": { "$eq": "trialing" }
    }
  }
}

Common Issues

IssueCauseSolution
Duplicate customersSame webhook fired twiceAdd idempotency check with subscription ID
Missing customer nameStripe customer has no nameFall back to email prefix
QuickBooks sync failedCustomer already existsCheck before create, update instead
Email not sentInvalid email formatValidate email in transform step
Wrong plan amountAmount is in centsDivide by 100 for display