Skip to main content
โšก Calmops

SaaS Onboarding: User Provisioning, API Integration

Introduction

Onboarding is the first impression that determines customer success. 60% of customers cite onboarding quality as a key factor in retention. This guide covers building automated, scalable onboarding systems.

Key Statistics:

  • 63% of customers decide to continue or churn within 60 seconds
  • Automated onboarding reduces time-to-value by 70%
  • Companies with great onboarding have 2x higher LTV
  • 40% of churn happens within first 90 days

What Is SaaS Onboarding and Why It Matters

SaaS onboarding is the process of getting new customers up and running with your product. It’s not just about creating an accountโ€”it’s about helping users understand your product, configure it for their needs, and achieve their first “aha moment.”

The Onboarding Funnel

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    SaaS Onboarding Funnel                          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                  โ”‚
โ”‚  1. Trial Sign-up (100%)                                         โ”‚
โ”‚     โ””โ”€โ”€ User creates account                                     โ”‚
โ”‚                                                                  โ”‚
โ”‚  2. Account Setup (80%)                                          โ”‚
โ”‚     โ””โ”€โ”€ User completes initial configuration                     โ”‚
โ”‚                                                                  โ”‚
โ”‚  3. First Value Moment (60%)                                     โ”‚
โ”‚     โ””โ”€โ”€ User achieves first meaningful outcome                   โ”‚
โ”‚                                                                  โ”‚
โ”‚  4. Active Usage (40%)                                           โ”‚
โ”‚     โ””โ”€โ”€ User regularly uses the product                          โ”‚
โ”‚                                                                  โ”‚
โ”‚  5. Paid Conversion (25%)                                        โ”‚
โ”‚     โ””โ”€โ”€ User upgrades to paid plan                               โ”‚
โ”‚                                                                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Common Onboarding Challenges

1. User Confusion

  • Users don’t understand how to use your product
  • Too many options overwhelm new users
  • Documentation is hard to find or understand

2. Configuration Complexity

  • Users struggle to configure the product for their needs
  • Integration setup is complicated
  • Data migration is error-prone

3. Time-to-Value

  • Users don’t see value quickly enough
  • Onboarding takes too long
  • Features are buried deep in the UI

4. Integration Gaps

  • Users need to connect to other systems
  • API documentation is unclear
  • Webhook setup is complex

User Provisioning

Understanding User Provisioning

User provisioning is the automated process of creating, managing, and deactivating user accounts across systems. In SaaS, provisioning ensures that when a user joins an organization, they get the right access to your product, and when they leave, their access is properly revoked.

SCIM vs Manual Provisioning

SCIM (System for Cross-domain Identity Management) is an open standard for automating user provisioning. It defines a RESTful API that identity providers (IdPs) can use to create, update, and delete user accounts.

Manual Provisioning involves administrators manually creating accounts through your UI or API. This is error-prone and doesn’t scale.

Why SCIM Matters

  • Automated Onboarding: New employees get immediate access when added to the IdP
  • Automated Offboarding: Access is revoked immediately when employees leave
  • Consistent User Data: User attributes are synchronized from the IdP
  • Reduced Admin Work: No manual account creation or deletion
  • Compliance: Audit trails for user provisioning activities

SCIM 2.0 Core Schema

SCIM defines a standard schema for user objects:

{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "id": "2819c223-7f76-453a-919d-413861904646",
  "externalId": "jsmith",
  "userName": "[email protected]",
  "name": {
    "formatted": "John Smith",
    "familyName": "Smith",
    "givenName": "John"
  },
  "emails": [
    {
      "value": "[email protected]",
      "type": "work",
      "primary": true
    }
  ],
  "active": true,
  "groups": [
    {
      "value": "e9e30dba-f08f-4109-a794-5445929a4568",
      "$ref": "https://example.com/scim/v2/Groups/e9e30dba-f08f-4109-a794-5445929a4568",
      "display": "Sales"
    }
  ],
  "meta": {
    "resourceType": "User",
    "created": "2010-01-23T00:00:00Z",
    "lastModified": "2010-01-23T00:00:00Z",
    "location": "https://example.com/scim/v2/Users/2819c223-7f76-453a-919d-413861904646",
    "version": "W/\"e180ee84f0671b1\""
  }
}

SCIM Endpoints

SCIM defines several endpoints for user management:

Endpoint Method Description
/scim/v2/Users GET List users with pagination
/scim/v2/Users POST Create a new user
/scim/v2/Users/{id} GET Get a specific user
/scim/v2/Users/{id} PUT Update a user (full replacement)
/scim/v2/Users/{id} PATCH Update a user (partial update)
/scim/v2/Users/{id} DELETE Delete a user
/scim/v2/Groups GET List groups
/scim/v2/Groups POST Create a new group

SCIM Implementation

#!/usr/bin/env python3
"""SCIM 2.0 server implementation."""

from flask import Flask, request, jsonify
import uuid

app = Flask(__name__)

# In-memory storage
users = {}
groups = {}

@app.route('/scim/v2/Users', methods=['GET'])
def list_users():
    """List users with pagination."""
    start_index = int(request.args.get('startIndex', 1))
    count = int(request.args.get('count', 100))
    
    user_list = list(users.values())[start_index-1:start_index-1+count]
    
    return jsonify({
        "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
        "totalResults": len(users),
        "startIndex": start_index,
        "itemsPerPage": count,
        "Resources": [format_user(u) for u in user_list]
    })

@app.route('/scim/v2/Users', methods=['POST'])
def create_user():
    """Create new user."""
    data = request.json
    
    user_id = str(uuid.uuid4())
    user = {
        "id": user_id,
        "userName": data.get('userName'),
        "name": data.get('name', {}),
        "emails": data.get('emails', []),
        "groups": data.get('groups', []),
        "active": data.get('active', True),
        "meta": {
            "resourceType": "User",
            "created": datetime.now().isoformat()
        }
    }
    
    users[user_id] = user
    
    # Provision resources for new user
    provision_user_resources(user)
    
    return jsonify(format_user(user)), 201

@app.route('/scim/v2/Users/<user_id>', methods=['PUT'])
def update_user(user_id):
    """Update user."""
    if user_id not in users:
        return jsonify({"error": "User not found"}), 404
    
    data = request.json
    users[user_id].update(data)
    
    return jsonify(format_user(users[user_id]))

@app.route('/scim/v2/Users/<user_id>', methods=['DELETE'])
def delete_user(user_id):
    """Delete user and deprovision."""
    if user_id not in users:
        return jsonify({"error": "User not found"}), 404
    
    # Deprovision user resources
    deprovision_user_resources(users[user_id])
    
    del users[user_id]
    return '', 204

def format_user(user):
    """Format user for SCIM response."""
    return {
        "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
        "id": user["id"],
        "userName": user["userName"],
        "name": user.get("name", {}),
        "emails": user.get("emails", []),
        "active": user.get("active", True),
        "meta": user.get("meta", {})
    }

def provision_user_resources(user):
    """Provision resources for new user."""
    # Create default workspace
    create_workspace(user)
    
    # Add to default team
    add_user_to_default_team(user)
    
    # Generate API keys
    create_api_keys(user)
    
    # Trigger welcome email
    send_welcome_email(user)

def deprovision_user_resources(user):
    """Remove user access and resources."""
    # Revoke API keys
    revoke_api_keys(user)
    
    # Archive user data
    archive_user_data(user)

JIT Provisioning

#!/usr/bin/env python3
"""Just-In-Time user provisioning."""

import jwt
from datetime import datetime, timedelta

class JITProvisioner:
    def __init__(self, config):
        self.secret_key = config['jwt_secret']
        self.idp = config['identity_provider']
        
    def process_saml_assertion(self, assertion):
        """Process SAML assertion and provision user."""
        
        # Extract user attributes
        user_attrs = {
            'email': assertion.get('email'),
            'name': assertion.get('name'),
            'groups': assertion.get('groups', []),
            'department': assertion.get('department'),
            'manager': assertion.get('manager')
        }
        
        # Find or create user
        user = self.find_user_by_email(user_attrs['email'])
        
        if not user:
            user = self.create_user(user_attrs)
        else:
            self.update_user(user, user_attrs)
        
        # Assign to groups based on IdP groups
        self.sync_groups(user, user_attrs['groups'])
        
        # Grant role based on department
        self.assign_role(user, user_attrs.get('department'))
        
        return user
    
    def process_jwt_token(self, token):
        """Process JWT token for JWT-based provisioning."""
        
        try:
            payload = jwt.decode(token, self.secret_key, algorithms=['HS256'])
            
            user_attrs = {
                'email': payload['email'],
                'name': payload.get('name'),
                'roles': payload.get('roles', []),
                'tenant_id': payload.get('tenant_id')
            }
            
            return self.provision_with_tenant(user_attrs, payload['tenant_id'])
            
        except jwt.InvalidTokenError as e:
            raise Unauthorized(str(e))

Provisioning Workflow

#!/usr/bin/env python3
"""Complete provisioning workflow."""

from datetime import datetime

class ProvisioningWorkflow:
    """Orchestrate user provisioning workflow."""
    
    def __init__(self, db, email_service, notification_service):
        self.db = db
        self.email = email_service
        self.notifications = notification_service
    
    def provision_user(self, user_data):
        """Complete user provisioning workflow."""
        
        # Step 1: Create user account
        user = self.create_user_account(user_data)
        
        # Step 2: Provision resources
        self.provision_resources(user)
        
        # Step 3: Send welcome email
        self.send_welcome_email(user)
        
        # Step 4: Create onboarding tasks
        self.create_onboarding_tasks(user)
        
        # Step 5: Notify team
        self.notify_team(user)
        
        return user
    
    def create_user_account(self, user_data):
        """Create user account in system."""
        
        # Create user record
        user = {
            'id': str(uuid.uuid4()),
            'email': user_data['email'],
            'name': user_data['name'],
            'tenant_id': user_data['tenant_id'],
            'role': user_data.get('role', 'user'),
            'status': 'pending',
            'created_at': datetime.utcnow(),
            'provisioned_at': None
        }
        
        self.db.users.insert(user)
        
        return user
    
    def provision_resources(self, user):
        """Provision resources for user."""
        
        # Create user workspace
        workspace = self.db.workspaces.insert({
            'user_id': user['id'],
            'name': f"{user['name']}'s Workspace",
            'created_at': datetime.utcnow()
        })
        
        # Create default team
        team = self.db.teams.find_one({
            'tenant_id': user['tenant_id'],
            'name': 'Default Team'
        })
        
        if not team:
            team = self.db.teams.insert({
                'tenant_id': user['tenant_id'],
                'name': 'Default Team',
                'created_at': datetime.utcnow()
            })
        
        # Add user to team
        self.db.team_members.insert({
            'team_id': team['id'],
            'user_id': user['id'],
            'role': 'member',
            'joined_at': datetime.utcnow()
        })
        
        # Generate API keys
        api_key = self.generate_api_key(user['id'])
        self.db.api_keys.insert({
            'user_id': user['id'],
            'key': api_key,
            'created_at': datetime.utcnow()
        })
    
    def send_welcome_email(self, user):
        """Send welcome email to new user."""
        
        template_data = {
            'user_name': user['name'],
            'product_name': 'Your SaaS Product',
            'onboarding_link': f"https://app.example.com/onboarding?user_id={user['id']}"
        }
        
        self.email.send(
            to=user['email'],
            subject='Welcome to Your SaaS Product!',
            template='welcome',
            template_data=template_data
        )
    
    def create_onboarding_tasks(self, user):
        """Create onboarding tasks for user."""
        
        tasks = [
            {
                'user_id': user['id'],
                'title': 'Complete Your Profile',
                'description': 'Add your company information and team members',
                'priority': 'high',
                'created_at': datetime.utcnow()
            },
            {
                'user_id': user['id'],
                'title': 'Connect Your First Integration',
                'description': 'Connect your favorite tools and services',
                'priority': 'medium',
                'created_at': datetime.utcnow()
            },
            {
                'user_id': user['id'],
                'title': 'Invite Your Team',
                'description': 'Collaborate with your team members',
                'priority': 'low',
                'created_at': datetime.utcnow()
            }
        ]
        
        self.db.tasks.insert_many(tasks)
    
    def notify_team(self, user):
        """Notify team about new user."""
        
        team_members = self.db.team_members.find({
            'team_id': user['team_id'],
            'role': 'admin'
        })
        
        for admin in team_members:
            self.notifications.send(
                user_id=admin['user_id'],
                type='new_user_joined',
                message=f"{user['name']} has joined your team",
                link=f"/users/{user['id']}"
            )
    
    def deprovision_user(self, user_id):
        """Complete user deprovisioning workflow."""
        
        # Step 1: Revoke access
        self.revoke_access(user_id)
        
        # Step 2: Archive data
        self.archive_data(user_id)
        
        # Step 3: Delete resources
        self.delete_resources(user_id)
        
        # Step 4: Update user status
        self.update_user_status(user_id, 'deactivated')
    
    def revoke_access(self, user_id):
        """Revoke all user access."""
        
        # Revoke API keys
        self.db.api_keys.update_many(
            {'user_id': user_id},
            {'$set': {'revoked': True, 'revoked_at': datetime.utcnow()}}
        )
        
        # Revoke sessions
        self.db.sessions.delete_many({'user_id': user_id})
        
        # Revoke OAuth tokens
        self.db.oauth_tokens.delete_many({'user_id': user_id})
    
    def archive_data(self, user_id):
        """Archive user data for compliance."""
        
        # Export user data
        user_data = self.db.users.find_one({'id': user_id})
        activities = self.db.activities.find({'user_id': user_id})
        
        archive = {
            'user_id': user_id,
            'data': {
                'user': user_data,
                'activities': list(activities)
            },
            'archived_at': datetime.utcnow()
        }
        
        self.db.archives.insert(archive)
    
    def delete_resources(self, user_id):
        """Delete user resources."""
        
        # Delete user workspace
        self.db.workspaces.delete_many({'user_id': user_id})
        
        # Delete user tasks
        self.db.tasks.delete_many({'user_id': user_id})
        
        # Delete user notifications
        self.db.notifications.delete_many({'user_id': user_id})

API Integration

Understanding API Integration

API integration is the process of connecting your SaaS product with other systems and services. This enables data flow between systems, automates workflows, and extends functionality.

Why API Integration Matters

  • Data Synchronization: Keep data consistent across systems
  • Workflow Automation: Automate repetitive tasks
  • Extended Functionality: Add features through integrations
  • Better User Experience: Seamless experience across tools
  • Competitive Advantage: Integration capabilities differentiate your product

Common Integration Patterns

1. REST API Integration

  • Standard HTTP methods (GET, POST, PUT, DELETE)
  • JSON or XML data format
  • Stateful or stateless communication

2. Webhook Integration

  • Event-driven architecture
  • Push notifications to external systems
  • Real-time data synchronization

3. OAuth Integration

  • Secure authorization without sharing credentials
  • Token-based access
  • Granular permission scopes

4. SCIM Integration

  • Automated user provisioning
  • Identity synchronization
  • Compliance and audit trails

API Design Best Practices

1. Version Your APIs

GET /api/v1/users
GET /api/v2/users

2. Use Consistent Naming

GET /api/v1/users          # List users
POST /api/v1/users         # Create user
GET /api/v1/users/{id}     # Get user
PUT /api/v1/users/{id}     # Update user
DELETE /api/v1/users/{id}  # Delete user

3. Implement Pagination

GET /api/v1/users?page=1&limit=20

4. Use Proper Status Codes

  • 200 OK: Success
  • 201 Created: Resource created
  • 400 Bad Request: Invalid request
  • 401 Unauthorized: Authentication required
  • 403 Forbidden: Insufficient permissions
  • 404 Not Found: Resource not found
  • 429 Too Many Requests: Rate limit exceeded
  • 500 Internal Server Error: Server error

5. Rate Limiting

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1612345678

Webhook System

#!/usr/bin/env python3
"""Webhook delivery system."""

import hmac
import hashlib
import requests
import time
from datetime import datetime
import json

class WebhookManager:
    def __init__(self, config):
        self.webhooks = {}
        self.retry_policy = config.get('retry_policy', {
            'max_retries': 3,
            'backoff_factor': 2,
            'timeout': 30
        })
        
    def register_webhook(self, event_type, url, secret, filters=None):
        """Register webhook endpoint."""
        
        webhook_id = generate_webhook_id()
        
        self.webhooks[webhook_id] = {
            'id': webhook_id,
            'event_type': event_type,
            'url': url,
            'secret': secret,
            'filters': filters or {},
            'active': True,
            'created_at': datetime.now().isoformat()
        }
        
        return webhook_id
    
    def deliver_event(self, event_type, payload):
        """Deliver event to registered webhooks."""
        
        # Find matching webhooks
        matching = [
            w for w in self.webhooks.values()
            if w['event_type'] == event_type and w['active']
        ]
        
        for webhook in matching:
            # Apply filters
            if not self.matches_filters(payload, webhook['filters']):
                continue
            
            # Sign payload
            signature = self.sign_payload(payload, webhook['secret'])
            
            # Deliver with retries
            self.deliver_with_retry(webhook, payload, signature)
    
    def sign_payload(self, payload, secret):
        """Generate HMAC signature."""
        
        payload_str = json.dumps(payload, sort_keys=True)
        signature = hmac.new(
            secret.encode(),
            payload_str.encode(),
            hashlib.sha256
        ).hexdigest()
        
        return f"sha256={signature}"
    
    def deliver_with_retry(self, webhook, payload, signature):
        """Deliver webhook with retry logic."""
        
        headers = {
            'Content-Type': 'application/json',
            'X-Webhook-Signature': signature,
            'X-Webhook-Event': webhook['event_type'],
            'X-Webhook-ID': webhook['id']
        }
        
        for attempt in range(self.retry_policy['max_retries']):
            try:
                response = requests.post(
                    webhook['url'],
                    json=payload,
                    headers=headers,
                    timeout=self.retry_policy['timeout']
                )
                
                if response.status_code < 400:
                    self.log_delivery(webhook, payload, 'success')
                    return
                    
            except requests.RequestException as e:
                self.log_delivery(webhook, payload, 'error', str(e))
            
            # Exponential backoff
            if attempt < self.retry_policy['max_retries'] - 1:
                time.sleep(
                    self.retry_policy['backoff_factor'] ** attempt
                )
        
        self.mark_webhook_failed(webhook)

Integration API Design

# OpenAPI specification for SaaS integration
openapi: 3.0.0
info:
  title: SaaS Integration API
  version: 1.0.0

paths:
  /api/v1/customers:
    post:
      summary: Create customer
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                email:
                  type: string
                plan:
                  type: string
                  enum: [free, pro, enterprise]
      responses:
        201:
          description: Customer created

  /api/v1/customers/{id}/subscription:
    post:
      summary: Update subscription
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                plan:
                  type: string
                addons:
                  type: array
                  items:
                    type: string

  /api/v1/webhooks:
    get:
      summary: List webhooks
    post:
      summary: Create webhook
    delete:
      summary: Delete webhook

Integration SDK Examples

#!/usr/bin/env python3
"""SaaS Integration SDK."""

import requests
from typing import Dict, List, Optional

class SaaSIntegrationSDK:
    """SDK for SaaS integration."""
    
    def __init__(self, api_key: str, base_url: str = "https://api.example.com"):
        self.api_key = api_key
        self.base_url = base_url
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
    
    def create_customer(self, customer_data: Dict) -> Dict:
        """Create a new customer."""
        
        response = requests.post(
            f"{self.base_url}/api/v1/customers",
            json=customer_data,
            headers=self.headers
        )
        
        response.raise_for_status()
        return response.json()
    
    def get_customer(self, customer_id: str) -> Dict:
        """Get customer details."""
        
        response = requests.get(
            f"{self.base_url}/api/v1/customers/{customer_id}",
            headers=self.headers
        )
        
        response.raise_for_status()
        return response.json()
    
    def update_customer(self, customer_id: str, customer_data: Dict) -> Dict:
        """Update customer information."""
        
        response = requests.put(
            f"{self.base_url}/api/v1/customers/{customer_id}",
            json=customer_data,
            headers=self.headers
        )
        
        response.raise_for_status()
        return response.json()
    
    def delete_customer(self, customer_id: str) -> bool:
        """Delete a customer."""
        
        response = requests.delete(
            f"{self.base_url}/api/v1/customers/{customer_id}",
            headers=self.headers
        )
        
        response.raise_for_status()
        return response.status_code == 204
    
    def list_customers(self, page: int = 1, limit: int = 20) -> Dict:
        """List customers with pagination."""
        
        response = requests.get(
            f"{self.base_url}/api/v1/customers",
            params={"page": page, "limit": limit},
            headers=self.headers
        )
        
        response.raise_for_status()
        return response.json()
    
    def create_webhook(self, webhook_data: Dict) -> Dict:
        """Create a new webhook."""
        
        response = requests.post(
            f"{self.base_url}/api/v1/webhooks",
            json=webhook_data,
            headers=self.headers
        )
        
        response.raise_for_status()
        return response.json()
    
    def list_webhooks(self) -> List[Dict]:
        """List all webhooks."""
        
        response = requests.get(
            f"{self.base_url}/api/v1/webhooks",
            headers=self.headers
        )
        
        response.raise_for_status()
        return response.json()
    
    def delete_webhook(self, webhook_id: str) -> bool:
        """Delete a webhook."""
        
        response = requests.delete(
            f"{self.base_url}/api/v1/webhooks/{webhook_id}",
            headers=self.headers
        )
        
        response.raise_for_status()
        return response.status_code == 204

Integration Testing

#!/usr/bin/env python3
"""Integration testing for SaaS API."""

import pytest
from unittest.mock import Mock, patch

class TestSaaSIntegration:
    """Test SaaS integration."""
    
    def setup_method(self):
        """Set up test fixtures."""
        self.sdk = SaaSIntegrationSDK("test-api-key")
    
    @patch('requests.post')
    def test_create_customer(self, mock_post):
        """Test customer creation."""
        
        # Mock response
        mock_response = Mock()
        mock_response.status_code = 201
        mock_response.json.return_value = {
            "id": "cust_123",
            "name": "Test Customer",
            "email": "[email protected]"
        }
        mock_post.return_value = mock_response
        
        # Test
        customer_data = {
            "name": "Test Customer",
            "email": "[email protected]",
            "plan": "pro"
        }
        
        result = self.sdk.create_customer(customer_data)
        
        assert result["id"] == "cust_123"
        assert result["name"] == "Test Customer"
        assert result["email"] == "[email protected]"
    
    @patch('requests.get')
    def test_get_customer(self, mock_get):
        """Test customer retrieval."""
        
        # Mock response
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "id": "cust_123",
            "name": "Test Customer",
            "email": "[email protected]"
        }
        mock_get.return_value = mock_response
        
        # Test
        result = self.sdk.get_customer("cust_123")
        
        assert result["id"] == "cust_123"
        assert result["name"] == "Test Customer"
    
    @patch('requests.put')
    def test_update_customer(self, mock_put):
        """Test customer update."""
        
        # Mock response
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "id": "cust_123",
            "name": "Updated Customer",
            "email": "[email protected]"
        }
        mock_put.return_value = mock_response
        
        # Test
        result = self.sdk.update_customer("cust_123", {
            "name": "Updated Customer",
            "email": "[email protected]"
        })
        
        assert result["name"] == "Updated Customer"

Onboarding Workflow

Automated Welcome Series

#!/usr/bin/env python3
"""Automated onboarding workflow."""

from datetime import datetime, timedelta

class OnboardingWorkflow:
    def __init__(self):
        self.steps = []
        
    def add_step(self, step_type, config):
        """Add onboarding step."""
        
        step = {
            'type': step_type,
            'config': config,
            'order': len(self.steps)
        }
        self.steps.append(step)
        return self
    
    def execute(self, user):
        """Execute onboarding for user."""
        
        for step in self.steps:
            self.execute_step(user, step)
    
    def execute_step(self, user, step):
        """Execute individual step."""
        
        if step['type'] == 'email':
            send_email(user, step['config'])
        elif step['type'] == 'task':
            create_task(user, step['config'])
        elif step['type'] == 'api_call':
            call_integration_api(user, step['config'])
        elif step['type'] == 'delay':
            schedule_next_step(user, step['config'])

# Build onboarding workflow
workflow = OnboardingWorkflow()

workflow.add_step('email', {
    'template': 'welcome',
    'delay': 0
})

workflow.add_step('task', {
    'title': 'Complete your profile',
    'description': 'Add your company information',
    'delay': timedelta(hours=1)
})

workflow.add_step('api_call', {
    'endpoint': '/api/v1/setup',
    'action': 'create_workspace',
    'delay': timedelta(hours=2)
})

workflow.add_step('email', {
    'template': 'getting_started_guide',
    'delay': timedelta(days=1)
})

workflow.add_step('task', {
    'title': 'Invite your team',
    'description': 'Collaborate with your team',
    'delay': timedelta(days=2)
})

Onboarding Metrics

#!/usr/bin/env python3
"""Onboarding metrics tracking."""

from datetime import datetime, timedelta

class OnboardingMetrics:
    """Track onboarding metrics."""
    
    def __init__(self, db):
        self.db = db
    
    def calculate_completion_rate(self, start_date: datetime, end_date: datetime) -> float:
        """Calculate onboarding completion rate."""
        
        total_users = self.db.users.count_documents({
            'created_at': {'$gte': start_date, '$lte': end_date}
        })
        
        completed_users = self.db.users.count_documents({
            'created_at': {'$gte': start_date, '$lte': end_date},
            'onboarding_complete': True
        })
        
        return (completed_users / total_users * 100) if total_users > 0 else 0
    
    def calculate_time_to_value(self, start_date: datetime, end_date: datetime) -> float:
        """Calculate average time to first value."""
        
        pipeline = [
            {'$match': {
                'created_at': {'$gte': start_date, '$lte': end_date},
                'first_value_at': {'$ne': None}
            }},
            {'$project': {
                'time_to_value': {
                    '$subtract': ['$first_value_at', '$created_at']
                }
            }},
            {'$group': {
                '_id': None,
                'avg_time_to_value': {'$avg': '$time_to_value'}
            }}
        ]
        
        result = list(self.db.users.aggregate(pipeline))
        
        if result:
            return result[0]['avg_time_to_value'] / 1000 / 60 / 60  # Convert to hours
        
        return 0
    
    def calculate_drop_off_points(self, start_date: datetime, end_date: datetime) -> list:
        """Identify where users drop off in onboarding."""
        
        pipeline = [
            {'$match': {
                'created_at': {'$gte': start_date, '$lte': end_date}
            }},
            {'$project': {
                'completed_steps': {'$size': {'$ifNull': ['$completed_steps', []]}},
                'total_steps': 5
            }},
            {'$group': {
                '_id': '$completed_steps',
                'count': {'$sum': 1}
            }},
            {'$sort': {'_id': 1}}
        ]
        
        return list(self.db.users.aggregate(pipeline))
    
    def get_nps_score(self, start_date: datetime, end_date: datetime) -> float:
        """Calculate Net Promoter Score."""
        
        nps_responses = self.db.nps_responses.count_documents({
            'created_at': {'$gte': start_date, '$lte': end_date}
        })
        
        promoters = self.db.nps_responses.count_documents({
            'created_at': {'$gte': start_date, '$lte': end_date},
            'score': {'$gte': 9}
        })
        
        detractors = self.db.nps_responses.count_documents({
            'created_at': {'$gte': start_date, '$lte': end_date},
            'score': {'$lte': 6}
        })
        
        return ((promoters - detractors) / nps_responses * 100) if nps_responses > 0 else 0

Onboarding Checklist

#!/usr/bin/env python3
"""Onboarding checklist system."""

from datetime import datetime

class OnboardingChecklist:
    """Manage onboarding checklist."""
    
    def __init__(self, db):
        self.db = db
        self.checklist_items = [
            {
                'id': 'profile_complete',
                'title': 'Complete Your Profile',
                'description': 'Add your name, company, and contact information',
                'order': 1,
                'required': True
            },
            {
                'id': 'team_invited',
                'title': 'Invite Your Team',
                'description': 'Add team members to collaborate',
                'order': 2,
                'required': False
            },
            {
                'id': 'workspace_configured',
                'title': 'Configure Your Workspace',
                'description': 'Set up your workspace preferences and settings',
                'order': 3,
                'required': True
            },
            {
                'id': 'integration_connected',
                'title': 'Connect Your First Integration',
                'description': 'Connect your favorite tools and services',
                'order': 4,
                'required': False
            },
            {
                'id': 'first_task_completed',
                'title': 'Complete Your First Task',
                'description': 'Create your first project or task',
                'order': 5,
                'required': True
            }
        ]
    
    def get_checklist(self, user_id: str) -> dict:
        """Get checklist status for user."""
        
        user = self.db.users.find_one({'id': user_id})
        
        if not user:
            return {'error': 'User not found'}
        
        checklist = {
            'user_id': user_id,
            'items': [],
            'completed_count': 0,
            'total_count': len(self.checklist_items),
            'completion_rate': 0
        }
        
        for item in self.checklist_items:
            item_status = {
                'id': item['id'],
                'title': item['title'],
                'description': item['description'],
                'order': item['order'],
                'required': item['required'],
                'completed': item['id'] in user.get('completed_steps', [])
            }
            
            checklist['items'].append(item_status)
            
            if item_status['completed']:
                checklist['completed_count'] += 1
        
        checklist['completion_rate'] = (
            checklist['completed_count'] / checklist['total_count'] * 100
        )
        
        return checklist
    
    def complete_step(self, user_id: str, step_id: str) -> bool:
        """Mark a step as complete."""
        
        user = self.db.users.find_one({'id': user_id})
        
        if not user:
            return False
        
        completed_steps = user.get('completed_steps', [])
        
        if step_id not in completed_steps:
            completed_steps.append(step_id)
            self.db.users.update_one(
                {'id': user_id},
                {'$set': {'completed_steps': completed_steps}}
            )
        
        return True

Onboarding Email Templates

#!/usr/bin/env python3
"""Onboarding email templates."""

class OnboardingEmailTemplates:
    """Email templates for onboarding."""
    
    def get_welcome_email(self, user_name: str) -> dict:
        """Get welcome email template."""
        
        return {
            'subject': f"Welcome to Your SaaS Product, {user_name}!",
            'html': f"""
                <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
                    <h1>Welcome, {user_name}!</h1>
                    <p>Thank you for signing up. We're excited to have you on board.</p>
                    <h2>What's Next?</h2>
                    <ol>
                        <li>Complete your profile</li>
                        <li>Invite your team members</li>
                        <li>Configure your workspace</li>
                        <li>Create your first project</li>
                    </ol>
                    <p>Need help? Check out our <a href="https://docs.example.com">documentation</a> 
                       or <a href="mailto:[email protected]">contact us</a>.</p>
                    <p>Best regards,<br>The Team</p>
                </div>
            """,
            'text': f"""
                Welcome, {user_name}!
                
                Thank you for signing up. We're excited to have you on board.
                
                What's Next?
                1. Complete your profile
                2. Invite your team members
                3. Configure your workspace
                4. Create your first project
                
                Need help? Check out our documentation at https://docs.example.com
                or contact us at [email protected].
                
                Best regards,
                The Team
            """
        }
    
    def get_first_value_email(self, user_name: str) -> dict:
        """Get first value email template."""
        
        return {
            'subject': "You've Achieved Your First Value!",
            'html': f"""
                <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
                    <h1>Congratulations, {user_name}!</h1>
                    <p>You've completed your first task and achieved your first value with our product!</p>
                    <p>Here are some tips to get even more value:</p>
                    <ul>
                        <li>Invite your team to collaborate</li>
                        <li>Connect your favorite integrations</li>
                        <li>Explore our advanced features</li>
                    </ul>
                    <p>Keep up the great work!</p>
                </div>
            """,
            'text': f"""
                Congratulations, {user_name}!
                
                You've achieved your first value with our product!
                
                Here are some tips to get even more value:
                - Invite your team to collaborate
                - Connect your favorite integrations
                - Explore our advanced features
                
                Keep up the great work!
            """
        }
    
    def get_onboarding_complete_email(self, user_name: str) -> dict:
        """Get onboarding complete email template."""
        
        return {
            'subject': "Onboarding Complete - You're All Set!",
            'html': f"""
                <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
                    <h1>Onboarding Complete, {user_name}!</h1>
                    <p>You've completed all onboarding steps. You're ready to get the most out of our product!</p>
                    <p>Here are some resources to help you succeed:</p>
                    <ul>
                        <li><a href="https://docs.example.com">Documentation</a></li>
                        <li><a href="https://blog.example.com">Blog</a></li>
                        <li><a href="https://community.example.com">Community</a></li>
                    </ul>
                    <p>Happy creating!</p>
                </div>
            """,
            'text': f"""
                Onboarding Complete, {user_name}!
                
                You've completed all onboarding steps. You're ready to get the most out of our product!
                
                Here are some resources to help you succeed:
                - Documentation: https://docs.example.com
                - Blog: https://blog.example.com
                - Community: https://community.example.com
                
                Happy creating!
            """
        }

External Resources


Comments