Skip to main content

SDK Installation & Setup Guide

Get up and running with StateSet SDKs in minutes. This guide covers everything from installation to advanced configuration across all supported languages and frameworks.
New to StateSet? Start with our 5-minute quickstart to understand the basics before diving into SDK installation.

🚀 Quick Start

Choose your language and get started in under 5 minutes:
npm install stateset-node

📦 Supported SDKs

StateSet provides official SDKs for the following languages:

🎯 Which SDK Should I Use?

Recommended: Node.js/TypeScript SDK
  • ✅ Best for React, Next.js, Vue, Angular
  • ✅ Full TypeScript support
  • ✅ Excellent async/await handling
  • ✅ Largest community and ecosystem
Recommended: Python SDK
  • ✅ Best for data processing and analytics
  • ✅ Native pandas/numpy integration
  • ✅ Perfect for Jupyter notebooks
  • ✅ Great for FastAPI/Django backends
Recommended: Ruby SDK
  • ✅ Native Rails integration
  • ✅ ActiveRecord-style syntax
  • ✅ Sidekiq/Resque job support
  • ✅ Convention over configuration
Recommended: PHP SDK
  • ✅ PSR-compliant implementation
  • ✅ WordPress plugin ready
  • ✅ Laravel service provider included
  • ✅ Composer autoloading

Node.js SDK

Prerequisites

1

Check Node.js Version

node --version
# Should output v16.0.0 or higher
Node.js 16+ is required. For older versions, use stateset-node@legacy
2

Verify Package Manager

npm --version   # Should be 7.0.0+
# OR
yarn --version  # Should be 1.22.0+
# OR
pnpm --version  # Should be 6.0.0+
3

Optional: TypeScript Setup

tsc --version
# Should output Version 4.5.0 or higher

Installation

  • npm
  • yarn
  • pnpm
# Install the main SDK
npm install stateset-node

# Install type definitions (if using TypeScript)
npm install --save-dev @types/node

# Install recommended utilities
npm install dotenv winston axios-retry

Quick Setup

1

Create Project Structure

mkdir my-stateset-app && cd my-stateset-app
npm init -y
npm install stateset-node dotenv

# Create project structure
mkdir -p src/{services,utils,config}
touch .env .env.example .gitignore
2

Configure Environment Variables

# .env
STATESET_API_KEY=sk_live_51H9x2C2QlDjKpM2WYw5...
STATESET_ENVIRONMENT=production
STATESET_WEBHOOK_SECRET=whsec_3rK9pL7nQ2xS5mT8...
STATESET_LOG_LEVEL=info
STATESET_TIMEOUT=30000
STATESET_MAX_RETRIES=3
# .env.example (commit this to git)
STATESET_API_KEY=your_api_key_here
STATESET_ENVIRONMENT=sandbox
STATESET_WEBHOOK_SECRET=your_webhook_secret_here
STATESET_LOG_LEVEL=info
STATESET_TIMEOUT=30000
STATESET_MAX_RETRIES=3
# .gitignore
node_modules/
.env
.env.local
dist/
*.log
3

Create Configuration Module

// src/config/stateset.js
import { StateSetClient } from 'stateset-node';
import dotenv from 'dotenv';

dotenv.config();

// Validate required environment variables
const requiredEnvVars = ['STATESET_API_KEY'];
for (const envVar of requiredEnvVars) {
  if (!process.env[envVar]) {
    throw new Error(`Missing required environment variable: ${envVar}`);
  }
}

// Create and export configured client
export const stateset = new StateSetClient({
  apiKey: process.env.STATESET_API_KEY,
  environment: process.env.STATESET_ENVIRONMENT || 'sandbox',
  timeout: parseInt(process.env.STATESET_TIMEOUT || '30000'),
  maxRetries: parseInt(process.env.STATESET_MAX_RETRIES || '3'),
  telemetry: true // Help us improve the SDK
});

// Export configuration for reference
export const config = {
  environment: process.env.STATESET_ENVIRONMENT || 'sandbox',
  logLevel: process.env.STATESET_LOG_LEVEL || 'info',
  webhookSecret: process.env.STATESET_WEBHOOK_SECRET
};

Basic Configuration

  • JavaScript (ES6)
  • TypeScript
  • CommonJS
// app.js
import { stateset, config } from './src/config/stateset.js';

// Test the connection with proper error handling
async function testConnection() {
  try {
    const health = await stateset.health.check();
    console.log('✅ Connected to StateSet:', {
      status: health.status,
      environment: config.environment,
      version: health.version
    });
    
    // Test basic API access
    const { data: orders } = await stateset.orders.list({ limit: 1 });
    console.log('✅ API Access verified:', orders.length, 'orders found');
    
  } catch (error) {
    console.error('❌ Connection failed:', {
      message: error.message,
      code: error.code,
      statusCode: error.statusCode
    });
    process.exit(1);
  }
}

// Run tests
testConnection();

Advanced Configuration

  • Custom HTTP Client
  • Proxy Configuration
  • Custom Logger
import { StateSetClient } from 'stateset-node';
import axios from 'axios';
import axiosRetry from 'axios-retry';

// Create custom axios instance
const httpClient = axios.create({
  timeout: 60000,
  headers: {
    'User-Agent': 'MyApp/1.0.0'
  }
});

// Configure retry logic
axiosRetry(httpClient, {
  retries: 5,
  retryDelay: axiosRetry.exponentialDelay,
  retryCondition: (error) => {
    return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
           error.response?.status === 429; // Retry on rate limit
  }
});

// Use custom HTTP client
const client = new StateSetClient({
  apiKey: process.env.STATESET_API_KEY,
  httpClient: httpClient
});

Framework Integration

  • Express.js
  • Next.js
  • NestJS
// server.js
import express from 'express';
import { StateSetClient } from 'stateset-node';
import morgan from 'morgan';
import helmet from 'helmet';

const app = express();

// Security and logging middleware
app.use(helmet());
app.use(morgan('combined'));
app.use(express.json());

// Initialize StateSet client
const stateset = new StateSetClient({
  apiKey: process.env.STATESET_API_KEY,
  environment: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox'
});

// StateSet middleware
app.use((req, res, next) => {
  req.stateset = stateset;
  next();
});

// Error handling middleware
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

// Routes
app.get('/api/orders', asyncHandler(async (req, res) => {
  const { page = 1, limit = 20, status } = req.query;
  
  const { data: orders, pagination } = await req.stateset.orders.list({
    page: parseInt(page),
    limit: parseInt(limit),
    status: status
  });
  
  res.json({
    success: true,
    data: orders,
    pagination
  });
}));

app.post('/api/orders', asyncHandler(async (req, res) => {
  const order = await req.stateset.orders.create({
    customer_id: req.body.customer_id,
    items: req.body.items,
    shipping_address: req.body.shipping_address
  });
  
  res.status(201).json({
    success: true,
    data: order
  });
}));

// Global error handler
app.use((err, req, res, next) => {
  console.error('Error:', err);
  
  if (err.name === 'StateSetError') {
    return res.status(err.statusCode || 400).json({
      success: false,
      error: {
        message: err.message,
        code: err.code,
        requestId: err.requestId
      }
    });
  }
  
  res.status(500).json({
    success: false,
    error: {
      message: 'Internal server error'
    }
  });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Python SDK

Prerequisites

1

Check Python Version

python --version
# Should output Python 3.8.0 or higher

# Check pip version
pip --version
# Should be version 20.0 or higher
Python 3.8+ is required. For older versions, use stateset-python==1.x
2

Set Up Virtual Environment

# Create virtual environment
python -m venv stateset-env

# Activate on macOS/Linux
source stateset-env/bin/activate

# Activate on Windows
stateset-env\Scripts\activate

Installation

  • pip
  • poetry
  • conda
  • requirements.txt
# Install StateSet SDK with all optional dependencies
pip install "stateset-python[all]"

# Or install core SDK only
pip install stateset-python

# Install specific extras
pip install "stateset-python[async]"  # For async support
pip install "stateset-python[pandas]" # For data analysis

Quick Setup

1

Create Project Structure

# Create project structure
mkdir my-stateset-app && cd my-stateset-app
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dependencies
pip install stateset-python python-dotenv

# Create project files
mkdir -p src/{services,utils,config}
touch .env .env.example .gitignore README.md
touch src/__init__.py src/config/__init__.py
2

Configure Environment

# .env
STATESET_API_KEY=sk_live_51H9x2C2QlDjKpM2WYw5...
STATESET_ENVIRONMENT=production
STATESET_WEBHOOK_SECRET=whsec_3rK9pL7nQ2xS5mT8...
STATESET_LOG_LEVEL=INFO
STATESET_TIMEOUT=30
STATESET_MAX_RETRIES=3
# .gitignore
__pycache__/
*.py[cod]
*$py.class
.env
.venv/
venv/
.pytest_cache/
.mypy_cache/
*.log
3

Create Configuration Module

# src/config/stateset.py
import os
import logging
from typing import Optional
from stateset import StateSetClient, AsyncStateSetClient
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

# Configure logging
logging.basicConfig(
    level=getattr(logging, os.getenv('STATESET_LOG_LEVEL', 'INFO')),
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# Validate required environment variables
REQUIRED_ENV_VARS = ['STATESET_API_KEY']
for var in REQUIRED_ENV_VARS:
    if not os.getenv(var):
        raise ValueError(f"Missing required environment variable: {var}")

# Configuration
class Config:
    API_KEY = os.getenv('STATESET_API_KEY')
    ENVIRONMENT = os.getenv('STATESET_ENVIRONMENT', 'sandbox')
    WEBHOOK_SECRET = os.getenv('STATESET_WEBHOOK_SECRET')
    TIMEOUT = int(os.getenv('STATESET_TIMEOUT', '30'))
    MAX_RETRIES = int(os.getenv('STATESET_MAX_RETRIES', '3'))

# Create client instances
def get_client() -> StateSetClient:
    """Get configured StateSet client"""
    return StateSetClient(
        api_key=Config.API_KEY,
        environment=Config.ENVIRONMENT,
        timeout=Config.TIMEOUT,
        max_retries=Config.MAX_RETRIES
    )

def get_async_client() -> AsyncStateSetClient:
    """Get configured async StateSet client"""
    return AsyncStateSetClient(
        api_key=Config.API_KEY,
        environment=Config.ENVIRONMENT,
        timeout=Config.TIMEOUT,
        max_retries=Config.MAX_RETRIES
    )

# Export configured client
client = get_client()
async_client = get_async_client()

Basic Configuration

  • Synchronous
  • Asynchronous
  • Type Hints
# app.py
from src.config.stateset import client, Config, logger

def test_connection():
    """Test StateSet connection and basic API access"""
    try:
        # Test health check
        health = client.health.check()
        logger.info(f"✅ Connected to StateSet: {health}")
        
        # Test API access
        orders = client.orders.list(limit=1)
        logger.info(f"✅ API Access verified: {len(orders.data)} orders found")
        
        # Display configuration
        logger.info(f"Environment: {Config.ENVIRONMENT}")
        logger.info(f"Timeout: {Config.TIMEOUT}s")
        
        return True
        
    except Exception as e:
        logger.error(f"❌ Connection failed: {str(e)}")
        if hasattr(e, 'status_code'):
            logger.error(f"Status Code: {e.status_code}")
        if hasattr(e, 'request_id'):
            logger.error(f"Request ID: {e.request_id}")
        return False

if __name__ == "__main__":
    if test_connection():
        logger.info("StateSet SDK is properly configured!")
    else:
        exit(1)

Framework Integration

  • Flask
  • FastAPI
  • Django
# app.py
from flask import Flask, jsonify, request, g
from functools import wraps
from src.config.stateset import client, Config, logger
import traceback

app = Flask(__name__)
app.config['JSON_SORT_KEYS'] = False

# Error handling decorator
def handle_errors(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except Exception as e:
            logger.error(f"Error in {f.__name__}: {str(e)}")
            logger.error(traceback.format_exc())
            
            if hasattr(e, 'status_code'):
                status_code = e.status_code
            else:
                status_code = 500
            
            return jsonify({
                'success': False,
                'error': {
                    'message': str(e),
                    'type': type(e).__name__
                }
            }), status_code
    return decorated_function

# Routes
@app.route('/api/orders', methods=['GET'])
@handle_errors
def get_orders():
    page = int(request.args.get('page', 1))
    limit = int(request.args.get('limit', 20))
    status = request.args.get('status')
    
    orders = client.orders.list(
        page=page,
        limit=limit,
        status=status
    )
    
    return jsonify({
        'success': True,
        'data': orders.data,
        'pagination': {
            'page': orders.page,
            'limit': orders.limit,
            'total': orders.total
        }
    })

@app.route('/api/orders', methods=['POST'])
@handle_errors
def create_order():
    data = request.get_json()
    
    # Validate required fields
    required_fields = ['customer_id', 'items']
    for field in required_fields:
        if field not in data:
            return jsonify({
                'success': False,
                'error': f'Missing required field: {field}'
            }), 400
    
    order = client.orders.create(**data)
    
    return jsonify({
        'success': True,
        'data': order
    }), 201

@app.route('/api/orders/<order_id>', methods=['GET'])
@handle_errors
def get_order(order_id):
    order = client.orders.retrieve(order_id)
    return jsonify({
        'success': True,
        'data': order
    })

@app.route('/api/health', methods=['GET'])
def health_check():
    try:
        health = client.health.check()
        return jsonify({
            'status': 'healthy',
            'stateset': health,
            'environment': Config.ENVIRONMENT
        })
    except Exception as e:
        return jsonify({
            'status': 'unhealthy',
            'error': str(e)
        }), 503

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Ruby SDK

Prerequisites

1

Check Ruby Version

ruby --version
# Should output ruby 2.7.0 or higher

# Check gem version
gem --version
# Should be version 3.0.0 or higher

# Check bundler
bundle --version
# Should be version 2.0.0 or higher
Ruby 2.7+ is required. For older versions, use stateset-ruby v1.x
2

Install Dependencies

# Install bundler if not present
gem install bundler

# Optional: Install development tools
gem install pry rubocop rspec

Installation

  • Gemfile
  • Direct Install
  • From Source
# Gemfile
source 'https://rubygems.org'

# StateSet SDK
gem 'stateset-ruby', '~> 2.0'

# Environment management
gem 'dotenv-rails', groups: [:development, :test] # For Rails
# OR
gem 'dotenv', groups: [:development, :test] # For non-Rails

# Optional dependencies
gem 'faraday-retry' # Advanced retry logic
gem 'concurrent-ruby' # For async operations
bundle install

Quick Setup

1

Create Project Structure

# Create new Ruby project
mkdir my-stateset-app && cd my-stateset-app

# Initialize bundler
bundle init

# Create project structure
mkdir -p lib/stateset config spec
touch .env .env.example .gitignore README.md
touch config/stateset.rb lib/stateset_service.rb
2

Configure Environment

# .env
STATESET_API_KEY=sk_live_51H9x2C2QlDjKpM2WYw5...
STATESET_ENVIRONMENT=production
STATESET_WEBHOOK_SECRET=whsec_3rK9pL7nQ2xS5mT8...
STATESET_LOG_LEVEL=info
STATESET_TIMEOUT=30
STATESET_MAX_RETRIES=3
# .gitignore
*.gem
*.rbc
/.config
/coverage/
/spec/reports/
/tmp/
.env
.env.local
*.log
.DS_Store
3

Create Configuration

# config/stateset.rb
require 'stateset'
require 'dotenv/load'
require 'logger'

# Configure logger
Stateset.logger = Logger.new(STDOUT)
Stateset.logger.level = ENV.fetch('STATESET_LOG_LEVEL', 'INFO')

# Validate required environment variables
required_vars = ['STATESET_API_KEY']
missing_vars = required_vars.select { |var| ENV[var].nil? || ENV[var].empty? }

unless missing_vars.empty?
  raise "Missing required environment variables: #{missing_vars.join(', ')}"
end

# Configure StateSet client
Stateset.configure do |config|
  config.api_key = ENV['STATESET_API_KEY']
  config.environment = ENV.fetch('STATESET_ENVIRONMENT', 'sandbox')
  config.timeout = ENV.fetch('STATESET_TIMEOUT', '30').to_i
  config.max_retries = ENV.fetch('STATESET_MAX_RETRIES', '3').to_i
  config.webhook_secret = ENV['STATESET_WEBHOOK_SECRET']
  
  # Optional: Configure custom Faraday middleware
  config.configure_faraday do |faraday|
    faraday.request :retry, max: 3
    faraday.use :instrumentation, name: 'stateset.request'
  end
end

# Test connection helper
module StatesetHelper
  def self.test_connection
    begin
      health = Stateset::Health.check
      Stateset.logger.info "✅ Connected to StateSet: #{health['status']}"
      
      # Test API access
      orders = Stateset::Order.list(limit: 1)
      Stateset.logger.info "✅ API Access verified: #{orders.data.length} orders found"
      
      true
    rescue => e
      Stateset.logger.error "❌ Connection failed: #{e.message}"
      false
    end
  end
end

Basic Configuration

  • Simple Usage
  • Service Class
  • Async Operations
# app.rb
require_relative 'config/stateset'

# Test the connection
if StatesetHelper.test_connection
  puts "StateSet is properly configured!"
else
  exit 1
end

$client = getStateSetClient()

begin
  # Create a customer
  customer = Stateset::Customer.create(
    email: 'test@example.com',
    name: 'Test Customer',
    metadata: { source: 'ruby_sdk' }
  )
  puts "Created customer: #{customer.id}"
  
  # Create an order
  order = Stateset::Order.create(
    customer_id: customer.id,
    items: [
      { product_id: 'prod_123', quantity: 2, price: 1999 }
    ],
    shipping_address: {
      line1: '123 Main St',
      city: 'San Francisco',
      state: 'CA',
      postal_code: '94105',
      country: 'US'
    }
  )
  puts "Created order: #{order.id}"
  
  # List recent orders
  orders = Stateset::Order.list(limit: 10, status: 'pending')
  orders.data.each do |order|
    puts "Order #{order.id}: #{order.status} - $#{order.total / 100.0}"
  end
  
rescue Stateset::Error => e
  puts "StateSet Error: #{e.message}"
  puts "Error Code: #{e.code}" if e.code
  puts "Request ID: #{e.request_id}" if e.request_id
end

Framework Integration

  • Rails
  • Sinatra
# config/initializers/stateset.rb
require 'stateset'

Rails.application.config.before_initialize do
  Stateset.configure do |config|
    config.api_key = Rails.application.credentials.stateset[:api_key]
    config.environment = Rails.env.production? ? 'production' : 'sandbox'
    config.logger = Rails.logger
    config.timeout = 30
    
    # Add Rails-specific middleware
    config.configure_faraday do |faraday|
      faraday.request :retry, max: 3
      faraday.use :instrumentation, name: 'stateset.request'
    end
  end
end

# app/services/stateset_service.rb
class StatesetService
  include ActiveSupport::Rescuable
  
  rescue_from Stateset::Error, with: :handle_stateset_error
  
  def create_order(user, items, shipping_address)
    order = Stateset::Order.create(
      customer_id: user.stateset_customer_id,
      items: items.map(&:to_stateset_item),
      shipping_address: shipping_address.to_stateset_format,
      metadata: {
        user_id: user.id,
        created_via: 'rails_app'
      }
    )
    
    # Store order reference
    user.orders.create!(
      stateset_order_id: order.id,
      total: order.total,
      status: order.status
    )
    
    order
  end
  
  def sync_order_status(local_order)
    stateset_order = Stateset::Order.retrieve(local_order.stateset_order_id)
    
    if local_order.status != stateset_order.status
      local_order.update!(
        status: stateset_order.status,
        updated_at: Time.at(stateset_order.updated)
      )
    end
    
    stateset_order
  end
  
  private
  
  def handle_stateset_error(error)
    Rails.logger.error "StateSet Error: #{error.message}"
    Bugsnag.notify(error) if defined?(Bugsnag)
    
    case error
    when Stateset::RateLimitError
      raise CustomErrors::RateLimitExceeded
    when Stateset::AuthenticationError
      raise CustomErrors::ServiceUnavailable
    else
      raise error
    end
  end
end

# app/controllers/api/orders_controller.rb
class Api::OrdersController < ApplicationController
  before_action :authenticate_user!
  
  def index
    orders = current_user.orders.includes(:items)
    
    # Optionally sync with StateSet
    if params[:sync] == 'true'
      service = StatesetService.new
      orders.each { |order| service.sync_order_status(order) }
    end
    
    render json: orders
  end
  
  def create
    service = StatesetService.new
    order = service.create_order(
      current_user,
      order_params[:items],
      order_params[:shipping_address]
    )
    
    render json: order, status: :created
  rescue Stateset::Error => e
    render json: { error: e.message }, status: :unprocessable_entity
  end
  
  private
  
  def order_params
    params.require(:order).permit(
      items: [:product_id, :quantity, :price],
      shipping_address: [:line1, :line2, :city, :state, :postal_code, :country]
    )
  end
end

# app/jobs/stateset_webhook_job.rb
class StatesetWebhookJob < ApplicationJob
  def perform(event)
    case event['type']
    when 'order.updated'
      handle_order_update(event['data'])
    when 'order.completed'
      handle_order_completion(event['data'])
    end
  end
  
  private
  
  def handle_order_update(order_data)
    order = Order.find_by(stateset_order_id: order_data['id'])
    order&.update!(status: order_data['status'])
  end
  
  def handle_order_completion(order_data)
    order = Order.find_by(stateset_order_id: order_data['id'])
    OrderMailer.completion_notification(order).deliver_later if order
  end
end

PHP SDK

Prerequisites

1

Check PHP Version

php --version
# Should output PHP 8.0.0 or higher

# Check Composer version
composer --version
# Should be version 2.0.0 or higher

# Check required extensions
php -m | grep -E "(curl|json|mbstring)"
# Should show curl, json, and mbstring
PHP 8.0+ is required. For older versions, use stateset-php v1.x
2

Install Required Extensions

# Ubuntu/Debian
sudo apt-get install php8.1-curl php8.1-json php8.1-mbstring

# macOS with Homebrew
brew install php@8.1

# Windows with XAMPP/WAMP
# Extensions are usually pre-installed

Installation

  • Composer
  • Manual Installation
  • Laravel
# Install via Composer
composer require stateset/stateset-php

# Install with specific version
composer require stateset/stateset-php:^2.0

# Install development dependencies
composer require --dev phpunit/phpunit phpstan/phpstan

Quick Setup

1

Create Project Structure

# Create new PHP project
mkdir my-stateset-app && cd my-stateset-app
composer init

# Create project structure
mkdir -p src/{Services,Controllers,Models} config tests
touch .env .env.example .gitignore README.md
touch config/stateset.php src/Services/StateSetService.php
2

Configure Environment

# .env
STATESET_API_KEY=sk_live_51H9x2C2QlDjKpM2WYw5...
STATESET_ENVIRONMENT=production
STATESET_WEBHOOK_SECRET=whsec_3rK9pL7nQ2xS5mT8...
STATESET_LOG_LEVEL=info
STATESET_TIMEOUT=30
STATESET_MAX_RETRIES=3
# .gitignore
/vendor/
/.env
/.env.local
/composer.lock
/.phpunit.result.cache
/logs/
*.log
.DS_Store
3

Create Configuration

<?php
// config/stateset.php

require_once __DIR__ . '/../vendor/autoload.php';

use Stateset\StateSetClient;
use Stateset\Exception\StateSetException;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// Load environment variables
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();

// Validate required environment variables
$dotenv->required(['STATESET_API_KEY'])->notEmpty();

// Configure logger
$logger = new Logger('stateset');
$logger->pushHandler(new StreamHandler(
    __DIR__ . '/../logs/stateset.log', 
    $_ENV['STATESET_LOG_LEVEL'] ?? Logger::INFO
));

// Configure StateSet client
function getStateSetClient(): StateSetClient {
    global $logger;
    
    return new StateSetClient([
        'api_key' => $_ENV['STATESET_API_KEY'],
        'environment' => $_ENV['STATESET_ENVIRONMENT'] ?? 'sandbox',
        'timeout' => (int)($_ENV['STATESET_TIMEOUT'] ?? 30),
        'max_retries' => (int)($_ENV['STATESET_MAX_RETRIES'] ?? 3),
        'logger' => $logger,
        'webhook_secret' => $_ENV['STATESET_WEBHOOK_SECRET'] ?? null
    ]);
}

// Test connection helper
function testStateSetConnection(): bool {
    global $logger;
    
    try {
        $client = getStateSetClient();
        $health = $client->health->check();
        $logger->info("✅ Connected to StateSet: " . json_encode($health));
        
        // Test API access
        $orders = $client->orders->list(['limit' => 1]);
        $logger->info("✅ API Access verified: " . count($orders->data) . " orders found");
        
        return true;
    } catch (StateSetException $e) {
        $logger->error("❌ Connection failed: " . $e->getMessage());
        return false;
    }
}

Basic Configuration

  • Simple Usage
  • Service Class
  • Async Operations
<?php
// app.php
require_once __DIR__ . '/config/stateset.php';

use Stateset\Exception\StateSetException;

// Test the connection
if (!testStateSetConnection()) {
    exit(1);
}

$client = getStateSetClient();

try {
    // Create a customer
    $customer = $client->customers->create([
        'email' => 'test@example.com',
        'name' => 'Test Customer',
        'metadata' => ['source' => 'php_sdk']
    ]);
    echo "Created customer: {$customer->id}\n";
    
    // Create an order
    $order = $client->orders->create([
        'customer_id' => $customer->id,
        'items' => [
            [
                'product_id' => 'prod_123',
                'quantity' => 2,
                'price' => 1999
            ]
        ],
        'shipping_address' => [
            'line1' => '123 Main St',
            'city' => 'San Francisco',
            'state' => 'CA',
            'postal_code' => '94105',
            'country' => 'US'
        ]
    ]);
    echo "Created order: {$order->id}\n";
    
    // List recent orders
    $orders = $client->orders->list([
        'limit' => 10,
        'status' => 'pending'
    ]);
    
    foreach ($orders->data as $order) {
        $total = number_format($order->total / 100, 2);
        echo "Order {$order->id}: {$order->status} - \${$total}\n";
    }
    
} catch (StateSetException $e) {
    echo "StateSet Error: {$e->getMessage()}\n";
    if ($e->getCode()) {
        echo "Error Code: {$e->getCode()}\n";
    }
    if ($e->getRequestId()) {
        echo "Request ID: {$e->getRequestId()}\n";
    }
}

Framework Integration

  • Laravel
  • WordPress
<?php
// config/stateset.php

return [
    'api_key' => env('STATESET_API_KEY'),
    'environment' => env('STATESET_ENVIRONMENT', 'sandbox'),
    'webhook_secret' => env('STATESET_WEBHOOK_SECRET'),
    'timeout' => env('STATESET_TIMEOUT', 30),
    'max_retries' => env('STATESET_MAX_RETRIES', 3),
];
<?php
// app/Providers/StateSetServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Stateset\StateSetClient;

class StateSetServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(StateSetClient::class, function ($app) {
            return new StateSetClient([
                'api_key' => config('stateset.api_key'),
                'environment' => config('stateset.environment'),
                'timeout' => config('stateset.timeout'),
                'max_retries' => config('stateset.max_retries'),
            ]);
        });
    }
    
    public function boot()
    {
        $this->publishes([
            __DIR__.'/../../config/stateset.php' => config_path('stateset.php'),
        ], 'config');
    }
}
<?php
// app/Services/StateSetService.php

namespace App\Services;

use Stateset\StateSetClient;
use Stateset\Exception\StateSetException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;

class StateSetService
{
    protected StateSetClient $client;
    
    public function __construct(StateSetClient $client)
    {
        $this->client = $client;
    }
    
    public function createOrder(array $data)
    {
        try {
            $order = $this->client->orders->create($data);
            
            // Cache the order
            Cache::put("order_{$order->id}", $order, now()->addMinutes(5));
            
            // Dispatch event
            event(new \App\Events\OrderCreated($order));
            
            return $order;
        } catch (StateSetException $e) {
            Log::error('StateSet order creation failed', [
                'error' => $e->getMessage(),
                'data' => $data
            ]);
            throw $e;
        }
    }
    
    public function getOrder(string $orderId)
    {
        // Check cache first
        return Cache::remember("order_{$orderId}", 300, function () use ($orderId) {
            return $this->client->orders->retrieve($orderId);
        });
    }
}
<?php
// app/Http/Controllers/Api/OrderController.php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Services\StateSetService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class OrderController extends Controller
{
    protected StateSetService $statesetService;
    
    public function __construct(StateSetService $statesetService)
    {
        $this->statesetService = $statesetService;
    }
    
    public function index(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'page' => 'integer|min:1',
            'limit' => 'integer|min:1|max:100',
            'status' => 'string|in:pending,processing,completed,cancelled'
        ]);
        
        try {
            $orders = $this->statesetService->listOrders($validated);
            return response()->json($orders);
        } catch (\Exception $e) {
            return response()->json([
                'error' => $e->getMessage()
            ], 500);
        }
    }
    
    public function store(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'customer_id' => 'required|string',
            'items' => 'required|array',
            'items.*.product_id' => 'required|string',
            'items.*.quantity' => 'required|integer|min:1',
            'shipping_address' => 'required|array'
        ]);
        
        try {
            $order = $this->statesetService->createOrder($validated);
            return response()->json($order, 201);
        } catch (\Exception $e) {
            return response()->json([
                'error' => $e->getMessage()
            ], 422);
        }
    }
    
    public function show(string $id): JsonResponse
    {
        try {
            $order = $this->statesetService->getOrder($id);
            return response()->json($order);
        } catch (\Exception $e) {
            return response()->json([
                'error' => 'Order not found'
            ], 404);
        }
    }
}
<?php
// app/Http/Controllers/WebhookController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Stateset\Webhook;
use Illuminate\Support\Facades\Log;

class WebhookController extends Controller
{
    public function handle(Request $request)
    {
        $payload = $request->getContent();
        $sigHeader = $request->header('Stateset-Signature');
        $secret = config('stateset.webhook_secret');
        
        try {
            $event = Webhook::constructEvent($payload, $sigHeader, $secret);
            
            // Process webhook event
            switch ($event->type) {
                case 'order.updated':
                    $this->handleOrderUpdate($event->data);
                    break;
                case 'order.completed':
                    $this->handleOrderCompleted($event->data);
                    break;
                default:
                    Log::info('Unhandled webhook event type: ' . $event->type);
            }
            
            return response()->json(['received' => true]);
        } catch (\Exception $e) {
            Log::error('Webhook error: ' . $e->getMessage());
            return response()->json(['error' => 'Invalid signature'], 400);
        }
    }
    
    private function handleOrderUpdate($orderData)
    {
        // Update local database
        \App\Models\Order::where('stateset_id', $orderData->id)
            ->update(['status' => $orderData->status]);
    }
    
    private function handleOrderCompleted($orderData)
    {
        // Send notification email
        \App\Jobs\SendOrderCompletionEmail::dispatch($orderData->id);
    }
}

Environment Configuration

Development Environment

# .env.development
STATESET_API_KEY=sk_test_51H9x2C2QlDjKpM2WYw5...
STATESET_ENVIRONMENT=sandbox
STATESET_WEBHOOK_SECRET=whsec_test_3rK9pL7nQ2xS5mT8...
LOG_LEVEL=debug

Production Environment

# .env.production
STATESET_API_KEY=sk_live_51H9x2C2QlDjKpM2WYw5...
STATESET_ENVIRONMENT=production
STATESET_WEBHOOK_SECRET=whsec_prod_3rK9pL7nQ2xS5mT8...
LOG_LEVEL=info

Docker Configuration

# Dockerfile
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 3000

CMD ["npm", "start"]
# docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - STATESET_API_KEY=${STATESET_API_KEY}
      - STATESET_ENVIRONMENT=production
    volumes:
      - .:/app
      - /app/node_modules

Troubleshooting

Common Issues

Problem: 401 Unauthorized errorsSolutions:
# 1. Verify API key format
echo $STATESET_API_KEY
# Should start with sk_test_ or sk_live_

# 2. Check environment variables are loaded
node -e "console.log(process.env.STATESET_API_KEY)"

# 3. Verify API key permissions in dashboard
# https://app.stateset.com/settings/api-keys

# 4. Ensure correct environment
# sandbox keys: sk_test_*
# production keys: sk_live_*
Code fix:
// Ensure environment variables are loaded
import dotenv from 'dotenv';
dotenv.config({ path: '.env.local' }); // Try different path

// Debug API key
console.log('API Key exists:', !!process.env.STATESET_API_KEY);
console.log('API Key prefix:', process.env.STATESET_API_KEY?.substring(0, 7));
Problem: Requests timing out or ETIMEDOUT errorsSolutions:
// 1. Increase timeout
const client = new StateSetClient({
  apiKey: process.env.STATESET_API_KEY,
  timeout: 60000, // 60 seconds
  maxRetries: 5
});

// 2. Check network connectivity
// Run: curl https://api.stateset.com/v1/health

// 3. Configure proxy if behind firewall
import { HttpsProxyAgent } from 'https-proxy-agent';

const client = new StateSetClient({
  apiKey: process.env.STATESET_API_KEY,
  httpAgent: new HttpsProxyAgent(process.env.HTTP_PROXY)
});

// 4. Implement custom retry logic
async function retryableRequest(fn, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === retries - 1) throw error;
      await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
    }
  }
}
Problem: Cannot import StateSet modules or TypeScript errorsSolutions:
# 1. Clear cache and reinstall
rm -rf node_modules package-lock.json
npm cache clean --force
npm install

# 2. Check Node.js version
node --version  # Must be 16.0.0+

# 3. Verify installation
npm list stateset-node

# 4. Fix TypeScript paths
# tsconfig.json
{
  "compilerOptions": {
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true
  }
}

# 5. For ESM issues, add to package.json:
{
  "type": "module"
}
Problem: 429 Too Many Requests errorsSolutions:
// 1. Implement exponential backoff
import pRetry from 'p-retry';

const createOrderWithRetry = async (data) => {
  return pRetry(
    () => client.orders.create(data),
    {
      retries: 5,
      onFailedAttempt: error => {
        if (error.statusCode === 429) {
          console.log(`Rate limited, retrying in ${error.retriesLeft * 1000}ms`);
        }
      }
    }
  );
};

// 2. Implement request queuing
import PQueue from 'p-queue';

const queue = new PQueue({ 
  concurrency: 2, // Max 2 concurrent requests
  interval: 1000, // Per second
  intervalCap: 10 // Max 10 requests per second
});

// 3. Cache frequently accessed data
const cache = new Map();

async function getCachedOrder(id) {
  if (cache.has(id)) {
    return cache.get(id);
  }
  const order = await client.orders.retrieve(id);
  cache.set(id, order);
  return order;
}
Problem: Webhook signature verification failingSolutions:
// 1. Ensure raw body is used
app.use('/webhooks/stateset', express.raw({ type: 'application/json' }));

// 2. Correct signature verification
app.post('/webhooks/stateset', (req, res) => {
  const sig = req.headers['stateset-signature'];
  const body = req.body; // Must be raw Buffer
  
  try {
    const event = stateset.webhooks.constructEvent(
      body,
      sig,
      process.env.STATESET_WEBHOOK_SECRET
    );
    // Process event
    res.json({ received: true });
  } catch (err) {
    console.error('Webhook Error:', err.message);
    res.status(400).send(`Webhook Error: ${err.message}`);
  }
});

// 3. Debug webhook secret
console.log('Webhook secret exists:', !!process.env.STATESET_WEBHOOK_SECRET);
console.log('Secret prefix:', process.env.STATESET_WEBHOOK_SECRET?.substring(0, 7));

Platform-Specific Issues

  • Vercel
  • AWS Lambda
  • Docker
// vercel.json
{
  "functions": {
    "api/webhooks/stateset.js": {
      "maxDuration": 30
    }
  },
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Access-Control-Allow-Origin", "value": "*" }
      ]
    }
  ]
}

// api/webhooks/stateset.js
export const config = {
  api: {
    bodyParser: false, // Important for raw body
  },
};

Testing Your Integration

Unit Testing

  • Jest (Node.js)
  • Pytest (Python)
  • PHPUnit (PHP)
// __tests__/stateset.test.js
import { StateSetClient } from 'stateset-node';
import nock from 'nock';

describe('StateSet Integration', () => {
  let client;
  
  beforeEach(() => {
    client = new StateSetClient({
      apiKey: 'sk_test_123',
      environment: 'sandbox'
    });
  });
  
  afterEach(() => {
    nock.cleanAll();
  });
  
  test('should create an order', async () => {
    const orderData = {
      customer_id: 'cus_123',
      items: [{ product_id: 'prod_123', quantity: 1 }]
    };
    
    nock('https://api.stateset.com')
      .post('/v1/orders', orderData)
      .reply(201, {
        id: 'ord_123',
        ...orderData,
        status: 'pending'
      });
    
    const order = await client.orders.create(orderData);
    expect(order.id).toBe('ord_123');
    expect(order.status).toBe('pending');
  });
  
  test('should handle errors gracefully', async () => {
    nock('https://api.stateset.com')
      .post('/v1/orders')
      .reply(400, {
        error: {
          message: 'Invalid customer_id',
          code: 'invalid_request'
        }
      });
    
    await expect(client.orders.create({}))
      .rejects
      .toThrow('Invalid customer_id');
  });
});

Integration Testing

// integration-test.js
import { StateSetClient } from 'stateset-node';
import assert from 'assert';

const runIntegrationTests = async () => {
  console.log('🧪 Running StateSet Integration Tests...\n');
  
  const client = new StateSetClient({
    apiKey: process.env.STATESET_API_KEY,
    environment: 'sandbox'
  });
  
  const tests = [
    {
      name: 'Health Check',
      fn: async () => {
        const health = await client.health.check();
        assert(health.status === 'healthy', 'API should be healthy');
      }
    },
    {
      name: 'Create Customer',
      fn: async () => {
        const customer = await client.customers.create({
          email: `test-${Date.now()}@example.com`,
          name: 'Test Customer'
        });
        assert(customer.id, 'Customer should have an ID');
        return customer;
      }
    },
    {
      name: 'Create Order',
      fn: async (customer) => {
        const order = await client.orders.create({
          customer_id: customer.id,
          items: [
            {
              product_id: 'prod_test',
              quantity: 1,
              price: 1000
            }
          ]
        });
        assert(order.id, 'Order should have an ID');
        assert(order.status === 'pending', 'Order should be pending');
        return order;
      }
    },
    {
      name: 'Retrieve Order',
      fn: async (order) => {
        const retrieved = await client.orders.retrieve(order.id);
        assert(retrieved.id === order.id, 'Should retrieve the same order');
      }
    },
    {
      name: 'List Orders',
      fn: async () => {
        const orders = await client.orders.list({ limit: 5 });
        assert(Array.isArray(orders.data), 'Should return an array of orders');
        assert(orders.data.length <= 5, 'Should respect limit parameter');
      }
    }
  ];
  
  let context = {};
  
  for (const test of tests) {
    try {
      const result = await test.fn(context);
      if (result) context = result;
      console.log(`✅ ${test.name}`);
    } catch (error) {
      console.error(`❌ ${test.name}: ${error.message}`);
      process.exit(1);
    }
  }
  
  console.log('\n✨ All integration tests passed!');
};

runIntegrationTests().catch(console.error);

Load Testing

// load-test.js
import { StateSetClient } from 'stateset-node';
import pLimit from 'p-limit';

const runLoadTest = async () => {
  const client = new StateSetClient({
    apiKey: process.env.STATESET_API_KEY,
    environment: 'sandbox'
  });
  
  const limit = pLimit(10); // Max 10 concurrent requests
  const totalRequests = 100;
  const startTime = Date.now();
  
  console.log(`🚀 Starting load test with ${totalRequests} requests...\n`);
  
  const requests = Array.from({ length: totalRequests }, (_, i) => 
    limit(async () => {
      const start = Date.now();
      try {
        await client.orders.list({ limit: 1 });
        return { success: true, duration: Date.now() - start };
      } catch (error) {
        return { success: false, error: error.message, duration: Date.now() - start };
      }
    })
  );
  
  const results = await Promise.all(requests);
  const endTime = Date.now();
  
  // Calculate statistics
  const successful = results.filter(r => r.success).length;
  const failed = results.filter(r => !r.success).length;
  const avgDuration = results.reduce((sum, r) => sum + r.duration, 0) / results.length;
  const totalDuration = endTime - startTime;
  const requestsPerSecond = (totalRequests / totalDuration) * 1000;
  
  console.log('📊 Load Test Results:');
  console.log(`Total Requests: ${totalRequests}`);
  console.log(`Successful: ${successful} (${(successful/totalRequests*100).toFixed(1)}%)`);
  console.log(`Failed: ${failed}`);
  console.log(`Average Response Time: ${avgDuration.toFixed(0)}ms`);
  console.log(`Total Duration: ${totalDuration}ms`);
  console.log(`Requests/Second: ${requestsPerSecond.toFixed(1)}`);
  
  if (failed > 0) {
    console.log('\n❌ Failed requests:');
    results.filter(r => !r.success).forEach((r, i) => {
      console.log(`  ${i + 1}. ${r.error}`);
    });
  }
};

runLoadTest().catch(console.error);

Best Practices

Security

Never commit API keys or secrets to version control. Always use environment variables.
  • Environment Management
  • API Key Rotation
// config/stateset.js
import { StateSetClient } from 'stateset-node';

// Validate environment
const validateEnvironment = () => {
  const required = ['STATESET_API_KEY'];
  const missing = required.filter(key => !process.env[key]);
  
  if (missing.length > 0) {
    throw new Error(`Missing environment variables: ${missing.join(', ')}`);
  }
  
  // Validate API key format
  const apiKey = process.env.STATESET_API_KEY;
  if (!apiKey.startsWith('sk_test_') && !apiKey.startsWith('sk_live_')) {
    throw new Error('Invalid API key format');
  }
  
  // Ensure test keys aren't used in production
  if (process.env.NODE_ENV === 'production' && apiKey.startsWith('sk_test_')) {
    throw new Error('Test API keys cannot be used in production');
  }
};

validateEnvironment();

export const client = new StateSetClient({
  apiKey: process.env.STATESET_API_KEY,
  environment: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox'
});

Error Handling

Retry Strategy

import { StateSetClient } from 'stateset-node';
import retry from 'async-retry';

const client = new StateSetClient({
  apiKey: process.env.STATESET_API_KEY,
  maxRetries: 0 // Disable built-in retry
});

// Custom retry with exponential backoff
async function resilientRequest(operation) {
  return retry(
    async (bail) => {
      try {
        return await operation();
      } catch (error) {
        // Don't retry client errors
        if (error.statusCode >= 400 && error.statusCode < 500) {
          bail(error);
        }
        throw error;
      }
    },
    {
      retries: 5,
      factor: 2,
      minTimeout: 1000,
      maxTimeout: 30000,
      onRetry: (error, attempt) => {
        console.log(`Attempt ${attempt} failed:`, error.message);
      }
    }
  );
}

// Usage
const order = await resilientRequest(() => 
  client.orders.create(orderData)
);

Circuit Breaker

import CircuitBreaker from 'opossum';

const options = {
  timeout: 3000,
  errorThresholdPercentage: 50,
  resetTimeout: 30000
};

const breaker = new CircuitBreaker(
  async (data) => client.orders.create(data),
  options
);

breaker.on('open', () => 
  console.log('Circuit breaker opened')
);

breaker.on('halfOpen', () => 
  console.log('Circuit breaker half-open')
);

// Usage with fallback
try {
  const order = await breaker.fire(orderData);
} catch (error) {
  if (breaker.opened) {
    // Use fallback behavior
    console.log('Service unavailable, using cache');
    return getCachedOrder(orderId);
  }
  throw error;
}

Performance Optimization

  • Connection Pooling
  • Request Batching
  • Caching Strategy
// Reuse HTTP connections
import https from 'https';

const agent = new https.Agent({
  keepAlive: true,
  keepAliveMsecs: 1000,
  maxSockets: 50,
  maxFreeSockets: 10,
  timeout: 60000
});

const client = new StateSetClient({
  apiKey: process.env.STATESET_API_KEY,
  httpsAgent: agent
});

Monitoring & Logging

// monitoring.js
import { StateSetClient } from 'stateset-node';
import winston from 'winston';
import { StatsD } from 'node-statsd';

// Configure logger
const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'stateset-error.log', level: 'error' }),
    new winston.transports.File({ filename: 'stateset-combined.log' })
  ]
});

// Configure metrics
const metrics = new StatsD({
  host: 'localhost',
  port: 8125,
  prefix: 'stateset.'
});

// Wrap client with monitoring
class MonitoredStateSetClient extends StateSetClient {
  async request(method, path, data) {
    const startTime = Date.now();
    const metricName = `api.${method.toLowerCase()}.${path.replace(/\//g, '.')}`;
    
    try {
      const result = await super.request(method, path, data);
      
      // Log success
      const duration = Date.now() - startTime;
      metrics.timing(metricName, duration);
      metrics.increment(`${metricName}.success`);
      
      logger.info('API Request Success', {
        method,
        path,
        duration,
        status: result.status
      });
      
      return result;
    } catch (error) {
      // Log error
      const duration = Date.now() - startTime;
      metrics.timing(metricName, duration);
      metrics.increment(`${metricName}.error`);
      
      logger.error('API Request Failed', {
        method,
        path,
        duration,
        error: {
          message: error.message,
          code: error.code,
          statusCode: error.statusCode,
          requestId: error.requestId
        }
      });
      
      throw error;
    }
  }
}

Next Steps

Now that you have StateSet SDK installed and configured:

Support & Resources

Need help? Our support team is available Monday-Friday, 9 AM - 5 PM PST. Enterprise customers have 24/7 support.