Skip to main content

Cancel Process

Overview

The CancelProcessInstanceCommand terminates a running, waiting, or error process instance. The process is immediately stopped and marked as cancelled. This action cannot be undone.

Irreversible Action

Cancelling a process permanently terminates it. The process cannot be resumed or restarted from its current position. Consider pausing instead if you need to temporarily stop execution.

API Endpoint

POST /api/core/cmd

Headers

Content-Type: application/json
Authorization: Bearer {access_token}
X-Tenant-ID: {tenant_id}

Request Structure

{
"cmd": "CancelProcessInstanceCommand",
"data": {
"instanceGuid": "abc-123-def-456",
"reason": "Customer withdrew application",
"cancelledBy": "user@example.com"
}
}

Request Fields

FieldTypeRequiredDescription
instanceGuidstringYesThe unique identifier of the process instance to cancel
reasonstringNoReason for cancellation (for audit trail)
cancelledBystringNoUser or system that initiated cancellation

When to Use

✅ Good Use Cases

  • User-Initiated Cancellation: Customer withdrew loan application
  • Business Rule Violation: Detected fraud or policy violation
  • Timeout: Process exceeded maximum allowed time
  • External Event: Related resource no longer exists
  • Error Recovery: Process in unrecoverable error state
  • Testing: Clean up test process instances

❌ When NOT to Use

  • Temporary Pause: Use pause/resume instead (if available)
  • Task Failure: Consider retry or skip instead
  • User Action Needed: Use UserTask to wait for input
  • Normal Completion: Let process complete naturally

Sample Requests

1. Customer Withdrawal

{
"cmd": "CancelProcessInstanceCommand",
"data": {
"instanceGuid": "loan-app-2024-001",
"reason": "Customer called to withdraw loan application - no longer needs funds",
"cancelledBy": "support@bank.com"
}
}

2. Policy Violation

{
"cmd": "CancelProcessInstanceCommand",
"data": {
"instanceGuid": "account-opening-456",
"reason": "Applicant found on sanctions list during KYC verification",
"cancelledBy": "compliance-system"
}
}

3. Timeout

{
"cmd": "CancelProcessInstanceCommand",
"data": {
"instanceGuid": "kyc-process-789",
"reason": "Process exceeded maximum 30-day limit without completion",
"cancelledBy": "timeout-monitor"
}
}

4. Error Recovery

{
"cmd": "CancelProcessInstanceCommand",
"data": {
"instanceGuid": "payment-process-321",
"reason": "Unrecoverable error in payment gateway integration - manual processing required",
"cancelledBy": "admin@bank.com"
}
}

Response Structure

Successfully Cancelled

{
"isSuccessful": true,
"message": "Process instance cancelled successfully.",
"statusCode": "00",
"data": {
"instanceGuid": "loan-app-2024-001",
"status": "cancelled",
"cancelledAt": "2024-12-19T10:45:00Z",
"cancellationDetails": {
"reason": "Customer called to withdraw loan application - no longer needs funds",
"cancelledBy": "support@bank.com",
"previousStatus": "waiting",
"currentActivityId": "Task_ApprovalDecision"
},
"state": {
"variables": {
"loanId": 789,
"customerId": 456,
"loanAmount": 50000,
"status": "cancelled"
},
"completedTasks": [
"StartEvent",
"Task_ValidateCustomer",
"Task_CreditCheck",
"Task_AssessRisk"
],
"currentNode": "Task_ApprovalDecision",
"duration": 1200000,
"startedAt": "2024-12-19T10:25:00Z",
"endedAt": "2024-12-19T10:45:00Z"
}
}
}

Cancelled from Error State

{
"isSuccessful": true,
"message": "Process instance cancelled successfully.",
"statusCode": "00",
"data": {
"instanceGuid": "payment-process-321",
"status": "cancelled",
"cancelledAt": "2024-12-19T10:50:00Z",
"cancellationDetails": {
"reason": "Unrecoverable error in payment gateway integration",
"cancelledBy": "admin@bank.com",
"previousStatus": "error",
"errorDetails": {
"taskId": "Task_ProcessPayment",
"taskName": "Process Payment",
"errorMessage": "Payment gateway returned 500 Internal Server Error after 5 retries"
}
},
"state": {
"variables": {
"paymentId": 12345,
"amount": 10000,
"retryCount": 5
},
"completedTasks": [
"StartEvent",
"Task_ValidatePayment"
]
}
}
}

Error Responses

Process Not Found

{
"isSuccessful": false,
"message": "Process instance 'invalid-guid' not found.",
"statusCode": "99",
"data": null
}

Process Already Completed

{
"isSuccessful": false,
"message": "Cannot cancel process instance - already completed.",
"statusCode": "99",
"data": {
"instanceGuid": "loan-app-2024-001",
"status": "completed",
"completedAt": "2024-12-19T09:00:00Z"
}
}

Process Already Cancelled

{
"isSuccessful": false,
"message": "Process instance is already cancelled.",
"statusCode": "99",
"data": {
"instanceGuid": "loan-app-2024-001",
"status": "cancelled",
"cancelledAt": "2024-12-19T08:00:00Z"
}
}

Process Lifecycle with Cancellation

Use Cases

1. User-Initiated Cancellation

async function handleCustomerWithdrawal(applicationId, customerId, reason) {
// Get the process instance for this application
const processInstance = await getProcessInstanceByApplication(applicationId);

if (!processInstance) {
throw new Error(`No active process found for application ${applicationId}`);
}

// Verify customer owns this application
const state = await getProcessState(processInstance.guid);
if (state.data.state.variables.customerId !== customerId) {
throw new Error("Unauthorized: Customer does not own this application");
}

// Cancel the process
const response = await cancelProcess({
instanceGuid: processInstance.guid,
reason: `Customer withdrawal: ${reason}`,
cancelledBy: `customer:${customerId}`
});

if (response.isSuccessful) {
// Update application status
await updateApplicationStatus(applicationId, 'WITHDRAWN');

// Send confirmation email
await sendEmail({
to: state.data.state.variables.customerEmail,
subject: "Application Withdrawn",
body: `Your loan application has been successfully withdrawn.`
});

// Log for audit
await auditLog.record({
action: "CUSTOMER_WITHDRAWAL",
applicationId,
customerId,
reason,
timestamp: new Date()
});
}

return response;
}

2. Automatic Timeout Monitoring

class ProcessTimeoutMonitor {
constructor(maxDuration = 30 * 24 * 60 * 60 * 1000) { // 30 days
this.maxDuration = maxDuration;
}

async monitorProcesses() {
const runningProcesses = await this.getActiveProcesses();

for (const process of runningProcesses) {
await this.checkTimeout(process);
}
}

async checkTimeout(process) {
const startTime = new Date(process.startedAt);
const now = new Date();
const duration = now - startTime;

if (duration > this.maxDuration) {
console.warn(`Process ${process.instanceGuid} exceeded timeout`);

await cancelProcess({
instanceGuid: process.instanceGuid,
reason: `Process exceeded maximum duration of ${this.maxDuration}ms`,
cancelledBy: "timeout-monitor"
});

// Alert operations team
await this.alertOperations({
severity: "WARNING",
message: `Process ${process.instanceGuid} cancelled due to timeout`,
process
});
}
}

async getActiveProcesses() {
// Query for running or waiting processes
return await db.query(`
SELECT instanceGuid, startedAt, status
FROM ProcessInstances
WHERE status IN ('running', 'waiting')
`);
}

async alertOperations(alert) {
// Send to operations monitoring system
console.log("ALERT:", alert);
}
}

// Run monitor every hour
const monitor = new ProcessTimeoutMonitor();
setInterval(() => monitor.monitorProcesses(), 60 * 60 * 1000);

3. Cancellation with Cleanup

async function cancelWithCleanup(instanceGuid, reason, cancelledBy) {
// Get current state before cancelling
const state = await getProcessState(instanceGuid);
const variables = state.data.state.variables;

// Cancel the process
const response = await cancelProcess({
instanceGuid,
reason,
cancelledBy
});

if (response.isSuccessful) {
// Perform cleanup operations
await cleanupAfterCancellation(variables);

// Send notifications
await notifyStakeholders(variables, reason);

// Update related records
await updateRelatedRecords(variables, 'CANCELLED');
}

return response;
}

async function cleanupAfterCancellation(variables) {
const cleanupTasks = [];

// Release reserved resources
if (variables.reservationId) {
cleanupTasks.push(
releaseReservation(variables.reservationId)
);
}

// Cancel scheduled tasks
if (variables.scheduledJobId) {
cleanupTasks.push(
cancelScheduledJob(variables.scheduledJobId)
);
}

// Cleanup temporary files
if (variables.tempFileIds) {
cleanupTasks.push(
deleteTempFiles(variables.tempFileIds)
);
}

// Execute all cleanup tasks
await Promise.all(cleanupTasks);
}

async function notifyStakeholders(variables, reason) {
// Notify customer
if (variables.customerEmail) {
await sendEmail({
to: variables.customerEmail,
subject: "Application Cancelled",
body: `Your application has been cancelled. Reason: ${reason}`
});
}

// Notify assigned staff
if (variables.assignedTo) {
await sendNotification({
userId: variables.assignedTo,
message: `Process ${variables.applicationId} cancelled: ${reason}`
});
}
}

4. Bulk Cancellation

async function bulkCancelProcesses(criteria, reason) {
// Find processes matching criteria
const processes = await findProcesses(criteria);

console.log(`Found ${processes.length} processes to cancel`);

const results = {
successful: [],
failed: []
};

for (const process of processes) {
try {
const response = await cancelProcess({
instanceGuid: process.instanceGuid,
reason: `Bulk cancellation: ${reason}`,
cancelledBy: "system-admin"
});

if (response.isSuccessful) {
results.successful.push(process.instanceGuid);
} else {
results.failed.push({
guid: process.instanceGuid,
error: response.message
});
}
} catch (error) {
results.failed.push({
guid: process.instanceGuid,
error: error.message
});
}
}

console.log(`Cancelled ${results.successful.length} processes`);
console.log(`Failed to cancel ${results.failed.length} processes`);

return results;
}

// Example: Cancel all processes for a specific customer
await bulkCancelProcesses(
{ customerId: 12345 },
"Customer account closed"
);

// Example: Cancel all processes older than 60 days
await bulkCancelProcesses(
{ olderThan: Date.now() - (60 * 24 * 60 * 60 * 1000) },
"Exceeded retention period"
);

Best Practices

1. Always Provide a Reason

Good:

{
"reason": "Customer called support at 10:15 AM to withdraw application. Stated they found better rates with another lender. Ref: TICKET-2024-5678",
"cancelledBy": "support@bank.com"
}

Bad:

{
"reason": "Cancelled"
}

2. Verify Before Cancelling

async function safeCancelProcess(instanceGuid, reason) {
// 1. Verify process exists and is cancellable
const state = await getProcessState(instanceGuid);

if (!state.isSuccessful) {
throw new Error("Process not found");
}

const status = state.data.status;

if (status === "completed") {
throw new Error("Cannot cancel completed process");
}

if (status === "cancelled") {
throw new Error("Process already cancelled");
}

// 2. Check for critical operations in progress
const currentTask = state.data.currentActivityId;
const criticalTasks = [
"Task_ProcessPayment",
"Task_TransferFunds",
"Task_CreateAccount"
];

if (criticalTasks.includes(currentTask)) {
console.warn(`Cancelling during critical task: ${currentTask}`);
// Maybe require additional confirmation
}

// 3. Cancel with full context
return await cancelProcess({
instanceGuid,
reason,
cancelledBy: getCurrentUser()
});
}

3. Implement Confirmation for User-Initiated Cancellations

async function cancelWithConfirmation(instanceGuid, reason) {
// Get process details
const state = await getProcessState(instanceGuid);
const variables = state.data.state.variables;

// Show confirmation dialog
const confirmed = await showConfirmation({
title: "Cancel Process?",
message: `Are you sure you want to cancel this ${variables.processType}?`,
details: [
`Application ID: ${variables.applicationId}`,
`Current Stage: ${state.data.currentActivityId}`,
`Started: ${state.data.startedAt}`,
`Completed Tasks: ${state.data.state.completedTasks.length}`
],
warning: "This action cannot be undone.",
confirmText: "Yes, Cancel Process",
cancelText: "No, Go Back"
});

if (!confirmed) {
return { cancelled: false, reason: "User declined confirmation" };
}

return await cancelProcess({
instanceGuid,
reason,
cancelledBy: getCurrentUser()
});
}

4. Log All Cancellations

async function cancelWithAudit(instanceGuid, reason, cancelledBy) {
const startTime = Date.now();

// Get state before cancellation
const beforeState = await getProcessState(instanceGuid);

// Perform cancellation
const response = await cancelProcess({
instanceGuid,
reason,
cancelledBy
});

// Log to audit system
await auditLog.record({
action: "CANCEL_PROCESS",
instanceGuid,
reason,
cancelledBy,
timestamp: new Date(),
duration: Date.now() - startTime,
beforeState: {
status: beforeState.data.status,
currentTask: beforeState.data.currentActivityId,
completedTasks: beforeState.data.state?.completedTasks?.length || 0
},
success: response.isSuccessful
});

// Alert if high-value process
if (beforeState.data.state?.variables?.loanAmount > 100000) {
await alertManagement({
severity: "HIGH",
message: `High-value loan process cancelled: ${instanceGuid}`,
amount: beforeState.data.state.variables.loanAmount,
reason
});
}

return response;
}

Notes

  • Cancellation is permanent and cannot be undone
  • Cancelled processes cannot be resumed or restarted
  • All process state is preserved for audit purposes
  • Can cancel processes in any state (running, waiting, error)
  • Cannot cancel already completed processes
  • Cannot cancel already cancelled processes
  • Reason and cancelledBy fields are optional but recommended for audit trails
  • Consider cleanup operations after cancellation (release resources, notify stakeholders)
  • Implement confirmation for user-initiated cancellations
  • Monitor cancellation patterns to identify process issues
  • Use bulk cancellation carefully - ensure proper error handling