WF-008: User Offboarding Workflow
Overview
This workflow automates the offboarding of users and organizations whose subscriptions have expired. It runs on a schedule, identifies accounts past their grace period, and performs systematic resource cleanup including workstation deletion, storage removal, and data anonymization.
Trigger: Scheduled (hourly) Duration: ~30-120 seconds per organization Related Workflows: WF-003 (Stripe Webhook triggers grace period)
Prerequisites
Before starting, ensure you have:
- Required tools installed
- Access to necessary resources
- Basic understanding of concepts
Verify setup:
# Verification command
Workflow Diagram

Step-by-Step Narrative
Step 1: Hourly Schedule Trigger
- Node: Hourly Check
- Type: Schedule Trigger
- Interval: Every 1 hour
- Actions:
- Fires automatically every hour
- Initiates check for expired subscriptions
- Runs 24/7 for continuous compliance
Step 2: Find Expired Subscriptions
- Node: Find Expired Subscriptions
- Type: PostgreSQL Select
- Table:
public.organizations - Query Condition:
subscription_status = 'suspended'updated_at < NOW() - INTERVAL '30 days'
- Actions:
- Identifies organizations 30+ days past due
- Returns list of org IDs for processing
- Skips organizations already offboarded
Step 3: Loop Through Organizations
- Node: Loop Organizations
- Type: Split In Batches
- Batch Size: 1
- Actions:
- Processes one organization at a time
- Ensures reliable cleanup with error isolation
- Continues even if one org fails
For Each Organization:
Step 4: Get Organization Workstations
- Node: Get Org Workstations
- Type: PostgreSQL Select
- Table:
public.workstations - Actions:
- Retrieves all workstations for the organization
- Gets GCP workstation IDs for deletion
- Identifies storage buckets to clean
Step 5-6: Delete Cloud Resources (Parallel)
Node 5: Delete GCP Workstation
- Type: HTTP Request (GCP Workstations API)
- Method: DELETE
- Actions:
- Calls GCP API to delete workstation
- Stops any running VM
- Frees allocated resources
Node 6: Delete User Storage
- Type: Google Cloud Storage
- Operation: Delete with prefix
- Actions:
- Deletes all files under
org-{id}/ - Removes project files, configs, cache
- Bucket remains for other orgs
- Deletes all files under
Step 7: Mark Workstations Deleted
- Node: Mark Workstations Deleted
- Type: PostgreSQL Update
- Table:
public.workstations - Actions:
- Updates status to
deleted - Records
deleted_attimestamp - Maintains audit trail
- Updates status to
Step 8: Anonymize Organization Users
- Node: Anonymize Org Users
- Type: PostgreSQL Update
- Table:
public.users - Condition: Users who are members of the organization
- Actions:
- Replaces email with
offboarded-{id}@coditect.local - Sets display name to "Offboarded User"
- Sets
is_active: false - Preserves user ID for audit integrity
- Replaces email with
Step 9: Mark Organization Offboarded
- Node: Mark Org Offboarded
- Type: PostgreSQL Update
- Table:
public.organizations - Actions:
- Sets
subscription_status: 'offboarded' - Replaces name with "Offboarded Organization"
- Records
deleted_attimestamp - Prevents any future access
- Sets
Step 10: Log Offboarding Event
- Node: Log Offboarding Event
- Type: PostgreSQL Insert
- Table:
public.audit_log - Actions:
- Creates immutable compliance record
- Records actor as
system - Documents reason:
subscription_expired - Notes 30-day grace period was honored
Step 11: Publish Offboard Event
- Node: Publish Offboard Event
- Type: Google Cloud Pub/Sub
- Topic:
billing-events - Actions:
- Publishes
organization.offboardedevent - Includes org ID and timestamp
- Enables downstream cleanup
- Triggers analytics update
- Publishes
Step 12: Continue Loop
- Node: Loop back to Step 3
- Actions:
- Returns to batch splitter
- Processes next organization
- Continues until all processed
Data Flow
Schedule Trigger → Find Expired Orgs
For each organization:
Input (from query):
{
"id": "org-uuid",
"name": "Expired Corp",
"subscription_status": "suspended",
"updated_at": "2023-12-15T10:00:00Z"
}
After processing:
{
"id": "org-uuid",
"name": "Offboarded Organization",
"subscription_status": "offboarded",
"deleted_at": "2024-01-15T10:30:00Z"
}
Users updated:
- email: offboarded-{id}@coditect.local
- display_name: Offboarded User
- is_active: false
Pub/Sub Event:
{
"event": "organization.offboarded",
"org_id": "org-uuid",
"timestamp": "2024-01-15T10:30:00Z"
}
Offboarding Timeline
| Day | Status | Action |
|---|---|---|
| 0 | Payment fails | WF-003 starts grace period |
| 1-7 | past_due | Daily email reminders |
| 8-14 | past_due | Workstation suspended (not deleted) |
| 15-30 | suspended | Final warning emails |
| 30+ | suspended | This workflow runs, full offboarding |
Resources Deleted
| Resource | Action | Recoverable? |
|---|---|---|
| GCP Workstation | Deleted via API | No |
| Cloud Storage files | Deleted with prefix | No |
| User PII | Anonymized | No (GDPR compliance) |
| Subscription | Status → offboarded | Re-subscribe only |
Resources Retained
| Resource | Reason | Retention Period |
|---|---|---|
| Billing records | Tax compliance | 7 years |
| Audit logs | Security compliance | 1 year |
| Invoice history | Legal requirements | 7 years |
| Anonymized user ID | Audit trail integrity | Indefinite |
Monitoring and Alerts
Prometheus Metrics:
offboarding_orgs_processed_totaloffboarding_duration_secondsoffboarding_errors_total
Alerting:
- Failed offboarding → Alert ops team
- High error rate → Page on-call
- Unexpected delay → Warning notification
Error Handling
| Error | Cause | Action |
|---|---|---|
| GCP API 404 | Workstation already deleted | Log and continue |
| GCP API 403 | Permission issue | Alert ops, skip org |
| DB connection failure | Network issue | Retry with backoff |
| Storage delete timeout | Large data volume | Async cleanup queue |
Error Isolation: Each organization processed independently. One failure doesn't stop processing of others.
Security Considerations
- Runs as system service account (not user context)
- Service account has minimal required permissions
- All actions logged to immutable audit trail
- No user data exposed in logs
- PII anonymized, not deleted (maintains audit integrity)
- 30-day grace period legally documented
Compliance Notes
This workflow ensures compliance with:
- GDPR Article 17: Right to erasure (anonymization fulfills this)
- GDPR Article 5(1)(e): Storage limitation (data not kept beyond necessity)
- SOC 2: Proper offboarding controls
- Data retention policy: Grace period documentation
Related Documents
- ADR-009: Multi-Tenant SaaS Architecture
- ADR-010: Cloud Workstations Architecture
- CODITECT Business Plan - Automated Lifecycle
- n8n Workflow JSON
Troubleshooting
Common Issue 1
Problem: Description of issue Solution: Steps to resolve
Common Issue 2
Problem: Description of issue Solution: Steps to resolve
Next Steps
After completing this guide:
- Explore: Additional related features
- Practice: Apply concepts in your project
- Reference: Related documentation