Skip to content

Link to billing, CRM & HR systems

Production-ready patterns for linking Scalekit organizations and users to Stripe, Salesforce, Workday and other enterprise systems using external identifiers

External identifiers enable seamless integration between Scalekit and your existing business systems. This guide provides practical patterns for implementing these integrations across common enterprise scenarios including billing platforms, CRM systems, HR systems, and multi-system workflows.

External IDs serve as the bridge between Scalekit’s authentication system and your business infrastructure. Common integration scenarios include:

  • Billing and subscription management - Link customers to payment platforms like Stripe, Chargebee
  • Customer relationship management - Sync with Salesforce, HubSpot, Pipedrive
  • Human resources systems - Connect with Workday, BambooHR, ADP
  • Internal tools and databases - Maintain consistency across custom applications
  • Multi-system orchestration - Coordinate data across multiple platforms

Connect organizations and users with your billing platform to track subscriptions, handle payment events, and maintain customer lifecycle data.

This example shows how to handle subscription updates by finding organizations using external IDs and updating their metadata accordingly.

Stripe webhook handler
// When a customer subscribes via Stripe
app.post('/stripe/webhook', async (req, res) => {
const event = req.body;
if (event.type === 'customer.subscription.updated') {
const customerId = event.data.object.customer;
// Find organization by external ID (Stripe customer ID)
const org = await scalekit.organization.getByExternalId(customerId);
if (org) {
// Update subscription metadata
await scalekit.organization.update(org.id, {
metadata: {
...org.metadata,
subscription_status: event.data.object.status,
plan_type: event.data.object.items.data[0].price.lookup_key,
last_billing_update: new Date().toISOString(),
subscription_current_period_end: new Date(event.data.object.current_period_end * 1000).toISOString()
}
});
// Use case: Automatically provision/deprovision features based on subscription status
if (event.data.object.status === 'active') {
await enablePremiumFeatures(org.id);
} else if (event.data.object.status === 'canceled') {
await disablePremiumFeatures(org.id);
}
}
}
// Handle customer deletion
if (event.type === 'customer.deleted') {
const customerId = event.data.object.id;
const org = await scalekit.organization.getByExternalId(customerId);
if (org) {
await scalekit.organization.update(org.id, {
metadata: {
...org.metadata,
billing_status: 'deleted',
deletion_date: new Date().toISOString()
}
});
}
}
res.status(200).send('OK');
});
  • Use Stripe customer IDs as external IDs for organizations to enable quick lookups during webhook processing
  • Store subscription metadata in organization records for immediate access in your application
  • Handle subscription lifecycle events (trial start, subscription active, canceled, past due)
  • Implement idempotency in webhook handlers to prevent duplicate processing
  • Use external IDs for user-level billing when implementing per-seat pricing models

Keep organization and user data synchronized between Scalekit and your CRM system to maintain consistent customer records and enable sales team workflows.

Salesforce sync integration
// Sync organization data with Salesforce
async function syncOrganizationWithCRM(organizationId, salesforceAccountId) {
try {
// Fetch account data from Salesforce
const crmData = await salesforce.getAccount(salesforceAccountId);
// Update Scalekit organization with CRM data
await scalekit.organization.update(organizationId, {
metadata: {
salesforce_account_id: salesforceAccountId,
industry: crmData.Industry,
annual_revenue: crmData.AnnualRevenue,
account_owner: crmData.Owner.Name,
account_type: crmData.Type,
company_size: crmData.NumberOfEmployees,
last_crm_sync: new Date().toISOString(),
crm_last_modified: crmData.LastModifiedDate
}
});
// Use case: Update user permissions based on account type
if (crmData.Type === 'Enterprise') {
await enableEnterpriseFeatures(organizationId);
}
} catch (error) {
console.error('CRM sync failed:', error);
// Log sync failure for monitoring
await logSyncFailure('salesforce', organizationId, error);
}
}
// Sync user data with Salesforce contacts
async function syncUserWithCRM(userId, organizationId, salesforceContactId) {
try {
const contactData = await salesforce.getContact(salesforceContactId);
await scalekit.user.updateUser(userId, {
metadata: {
salesforce_contact_id: salesforceContactId,
job_title: contactData.Title,
department: contactData.Department,
territory: contactData.Sales_Territory__c,
last_crm_contact_sync: new Date().toISOString()
}
});
} catch (error) {
console.error('User CRM sync failed:', error);
}
}
// Bidirectional sync: Update Salesforce when Scalekit data changes
async function updateCRMFromScalekit(organizationId) {
const org = await scalekit.organization.getById(organizationId);
if (org.metadata.salesforce_account_id) {
await salesforce.updateAccount(org.metadata.salesforce_account_id, {
Last_Login_Date__c: new Date().toISOString(),
Active_Users__c: await getUserCount(organizationId),
Subscription_Status__c: org.metadata.plan_type
});
}
}
  • Use CRM record IDs as external IDs to enable quick bidirectional lookups
  • Implement scheduled sync jobs to keep data fresh without overloading APIs
  • Handle API rate limits with exponential backoff and queuing
  • Store sync timestamps to enable incremental updates
  • Log sync failures for monitoring and debugging
  • Implement conflict resolution for bidirectional sync scenarios

Connect user records with HR systems to automate provisioning, maintain employee data, and handle organizational changes.

HR system integration example
// Sync user data with HR system during onboarding
async function syncNewEmployeeWithScalekit(employeeData) {
const { employee_id, email, first_name, last_name, department, start_date, manager_email } = employeeData;
// Find organization by domain or external ID
const domain = email.split('@')[1];
const organization = await scalekit.organization.getByDomain(domain);
if (organization) {
// Create user with HR system external ID
const { user } = await scalekit.user.createUserAndMembership(organization.id, {
email: email,
externalId: employee_id, // HR system employee ID
metadata: {
hr_employee_id: employee_id,
department: department,
start_date: start_date,
manager_email: manager_email,
employee_status: 'active',
hr_last_sync: new Date().toISOString()
},
userProfile: {
firstName: first_name,
lastName: last_name
},
sendInvitationEmail: true
});
// Use case: Assign department-based roles
await assignDepartmentRoles(user.id, department);
return user;
}
}
// Handle employee status changes
async function handleEmployeeStatusChange(employee_id, status) {
try {
// Find user by HR system external ID
const user = await scalekit.user.getUserByExternalId(organization.id, employee_id);
if (user) {
if (status === 'terminated') {
// Disable user access
await scalekit.user.updateUser(user.id, {
metadata: {
...user.metadata,
employee_status: 'terminated',
termination_date: new Date().toISOString()
}
});
// Remove from organization
await scalekit.user.removeMembership(user.id, organization.id);
} else if (status === 'on_leave') {
// Temporarily suspend access
await scalekit.user.updateUser(user.id, {
metadata: {
...user.metadata,
employee_status: 'on_leave',
leave_start_date: new Date().toISOString()
}
});
}
}
} catch (error) {
console.error('HR status sync failed:', error);
}
}

Orchestrate data across multiple systems using external IDs as the common identifier thread.

Multi-system workflow example
// Complete customer onboarding workflow
async function onboardNewCustomer(customerData) {
const { company_name, admin_email, plan_type, salesforce_account_id, stripe_customer_id } = customerData;
try {
// 1. Create organization in Scalekit
const organization = await scalekit.organization.create({
display_name: company_name,
external_id: stripe_customer_id, // Use billing system ID as primary external ID
metadata: {
plan_type: plan_type,
salesforce_account_id: salesforce_account_id,
stripe_customer_id: stripe_customer_id,
onboarding_status: 'pending',
created_date: new Date().toISOString()
}
});
// 2. Create admin user
const { user } = await scalekit.user.createUserAndMembership(organization.id, {
email: admin_email,
externalId: `${stripe_customer_id}_admin`, // Composite external ID
metadata: {
role_type: 'admin',
onboarding_step: 'account_created'
},
sendInvitationEmail: true
});
// 3. Update CRM with Scalekit IDs
await salesforce.updateAccount(salesforce_account_id, {
Scalekit_Organization_ID__c: organization.id,
Scalekit_Admin_User_ID__c: user.id,
Onboarding_Status__c: 'In Progress'
});
// 4. Configure billing in Stripe
await stripe.customers.update(stripe_customer_id, {
metadata: {
scalekit_org_id: organization.id,
scalekit_admin_user_id: user.id
}
});
// 5. Send onboarding notifications
await sendOnboardingEmail(admin_email, organization.id);
await notifySalesTeam(salesforce_account_id, 'customer_onboarded');
return { organization, user };
} catch (error) {
console.error('Customer onboarding failed:', error);
// Rollback logic here
throw error;
}
}

Implement robust error handling for external system integrations to ensure data consistency and reliability.

Robust integration error handling
// Utility function for retrying API calls with exponential backoff
async function retryWithBackoff(fn, maxRetries = 3, baseDelay = 1000) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
// Exponential backoff with jitter
const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Resilient external ID lookup
async function findOrganizationWithRetry(externalId) {
return retryWithBackoff(async () => {
const org = await scalekit.organization.getByExternalId(externalId);
if (!org) {
throw new Error(`Organization not found for external ID: ${externalId}`);
}
return org;
});
}
// Webhook processing with error handling
app.post('/webhook', async (req, res) => {
try {
const { external_id, event_type, data } = req.body;
// Find organization with retry logic
const organization = await findOrganizationWithRetry(external_id);
// Process the webhook data
await processWebhookEvent(organization, event_type, data);
res.status(200).json({ status: 'success' });
} catch (error) {
console.error('Webhook processing failed:', error);
// Queue for retry if it's a temporary failure
if (isRetryableError(error)) {
await queueWebhookForRetry(req.body);
res.status(202).json({ status: 'queued_for_retry' });
} else {
res.status(400).json({ status: 'error', message: error.message });
}
}
});
function isRetryableError(error) {
return error.code === 'NETWORK_ERROR' ||
error.code === 'RATE_LIMITED' ||
error.status >= 500;
}

When implementing external ID integrations, follow these security best practices:

Secure webhook handling
// Verify webhook signatures
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
// Rate limiting for webhook endpoints
const webhookLimiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1 minute
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many webhook requests from this IP'
});
app.post('/webhook', webhookLimiter, (req, res) => {
// Verify signature before processing
if (!verifyWebhookSignature(req.body, req.headers['x-signature'], process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook...
});
  • Validate external IDs before using them in database queries
  • Sanitize metadata to prevent injection attacks
  • Use prepared statements for database operations
  • Implement input validation for all external data
  • Log security events for monitoring and auditing

Implement comprehensive monitoring for external ID integrations to ensure system health and quick issue resolution.

Integration monitoring example
// Track integration health metrics
class IntegrationMonitor {
constructor() {
this.metrics = {
successful_syncs: 0,
failed_syncs: 0,
average_sync_time: 0,
last_successful_sync: null
};
}
async recordSyncAttempt(system, success, duration) {
if (success) {
this.metrics.successful_syncs++;
this.metrics.last_successful_sync = new Date();
} else {
this.metrics.failed_syncs++;
}
// Update average sync time
this.updateAverageSyncTime(duration);
// Send metrics to monitoring system
await this.sendMetrics(system, this.metrics);
}
updateAverageSyncTime(duration) {
const totalSyncs = this.metrics.successful_syncs + this.metrics.failed_syncs;
this.metrics.average_sync_time =
(this.metrics.average_sync_time * (totalSyncs - 1) + duration) / totalSyncs;
}
}
// Usage in integration functions
const monitor = new IntegrationMonitor();
async function syncWithExternalSystem(externalId, data) {
const startTime = Date.now();
let success = false;
try {
await performSync(externalId, data);
success = true;
} catch (error) {
console.error('Sync failed:', error);
throw error;
} finally {
const duration = Date.now() - startTime;
await monitor.recordSyncAttempt('external_system', success, duration);
}
}
  • Use meaningful, stable identifiers from your primary business system
  • Implement consistent naming conventions across all external IDs
  • Handle ID migration scenarios when external systems change
  • Validate external IDs before using them in operations
  • Implement retry logic with exponential backoff for API calls
  • Use webhooks for real-time sync and scheduled jobs for periodic reconciliation
  • Handle rate limits gracefully with queuing and backoff strategies
  • Monitor integration health with comprehensive metrics and alerting
  • Verify webhook signatures to ensure authenticity
  • Implement rate limiting on webhook endpoints
  • Validate and sanitize all external data
  • Audit integration activities for compliance requirements
  • Cache frequently accessed external ID mappings
  • Batch operations where possible to reduce API calls
  • Use appropriate timeouts for external API calls
  • Implement circuit breakers for unreliable external services

This integration approach enables seamless data flow between Scalekit and your business systems while maintaining security, reliability, and performance standards.