Skip to main content

Timer Events (Phase 4)

Timer Events enable time-based workflow automation, including scheduled tasks, timeouts, and recurring processes.

Overview

Timer events allow you to:

  • ✅ Schedule tasks at specific times
  • ✅ Set timeouts on user tasks and activities
  • ✅ Create recurring processes with cycle timers
  • ✅ Delay execution for specific durations
  • ✅ Implement SLA monitoring with non-interrupting timers

Timer Event Types

1. Timer Start Event

Start a process at a specific time or recurring schedule:

<!-- Start process daily at 2 AM -->
<bpmn:startEvent id="DailyStart" name="Daily at 2 AM">
<bpmn:timerEventDefinition>
<bpmn:timeCycle>0 2 * * *</bpmn:timeCycle>
</bpmn:timerEventDefinition>
</bpmn:startEvent>

<!-- Start process on specific date -->
<bpmn:startEvent id="ScheduledStart" name="Launch Date">
<bpmn:timerEventDefinition>
<bpmn:timeDate>2026-06-01T00:00:00Z</bpmn:timeDate>
</bpmn:timerEventDefinition>
</bpmn:startEvent>

2. Intermediate Timer Event

Pause process execution for a duration:

<bpmn:scriptTask id="SendReminder" name="Send Reminder"/>

<!-- Wait 24 hours -->
<bpmn:intermediateCatchEvent id="Wait24Hours" name="Wait 24 Hours">
<bpmn:timerEventDefinition>
<bpmn:timeDuration>P1D</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:intermediateCatchEvent>

<bpmn:scriptTask id="SendFollowUp" name="Send Follow-Up"/>

3. Boundary Timer Event (Timeout)

Set timeouts on tasks:

<bpmn:userTask id="CustomerResponse" name="Wait for Customer">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="FormKey" value="customer-response-form"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:userTask>

<!-- Timeout after 7 days (interrupting) -->
<bpmn:boundaryEvent id="ResponseTimeout"
attachedToRef="CustomerResponse"
cancelActivity="true">
<bpmn:timerEventDefinition>
<bpmn:timeDuration>P7D</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:boundaryEvent>

<!-- Handle timeout -->
<bpmn:scriptTask id="HandleTimeout" name="Handle Timeout">
<bpmn:script>
logger.warn('Customer response timeout after 7 days');

context.applicationStatus = 'EXPIRED';
context.expiryReason = 'CUSTOMER_NO_RESPONSE';

// Send notification
BankLingo.ExecuteCommand('SendEmail', {
to: context.customerEmail,
subject: 'Application Expired',
body: 'Your application has been closed due to no response.'
});
</bpmn:script>
</bpmn:scriptTask>

4. Non-Interrupting Timer (Reminder)

Send reminders without canceling the task:

<bpmn:userTask id="ManagerApproval" name="Manager Approval">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="FormKey" value="approval-form"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:userTask>

<!-- Send reminder after 24 hours (non-interrupting) -->
<bpmn:boundaryEvent id="ReminderTimer"
attachedToRef="ManagerApproval"
cancelActivity="false">
<bpmn:timerEventDefinition>
<bpmn:timeDuration>P1D</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:boundaryEvent>

<!-- Send reminder (task continues) -->
<bpmn:sendTask id="SendReminder" name="Send Reminder">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="messageType" value="email"/>
<custom:property name="to" value="context.managerEmail"/>
<custom:property name="subject" value="Reminder: Approval Pending"/>
<custom:property name="body" value="Loan approval pending for 24 hours"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:sendTask>

Timer Formats (ISO 8601)

The process engine supports three ISO 8601 timer formats:

1. Time Date (Specific Point in Time)

Schedule at exact date/time:

<!-- Specific date and time (UTC) -->
<bpmn:timeDate>2026-12-31T23:59:59Z</bpmn:timeDate>

<!-- With timezone offset -->
<bpmn:timeDate>2026-06-01T09:00:00+01:00</bpmn:timeDate>

<!-- Examples -->
<bpmn:timeDate>2026-01-15T14:30:00Z</bpmn:timeDate> <!-- Jan 15, 2026 at 2:30 PM UTC -->
<bpmn:timeDate>2026-03-20T00:00:00-05:00</bpmn:timeDate> <!-- Mar 20, 2026 midnight EST -->

Use Cases:

  • Product launch dates
  • Campaign start times
  • Scheduled maintenance windows
  • Contract effective dates

2. Time Duration (Relative Duration)

Wait for a specific period:

<!-- Format: P[n]Y[n]M[n]DT[n]H[n]M[n]S -->
<!-- P = Period, T = Time separator -->

<!-- Common durations -->
<bpmn:timeDuration>PT30M</bpmn:timeDuration> <!-- 30 minutes -->
<bpmn:timeDuration>PT1H</bpmn:timeDuration> <!-- 1 hour -->
<bpmn:timeDuration>PT2H30M</bpmn:timeDuration> <!-- 2 hours 30 minutes -->
<bpmn:timeDuration>P1D</bpmn:timeDuration> <!-- 1 day -->
<bpmn:timeDuration>P7D</bpmn:timeDuration> <!-- 7 days -->
<bpmn:timeDuration>P1W</bpmn:timeDuration> <!-- 1 week -->
<bpmn:timeDuration>P1M</bpmn:timeDuration> <!-- 1 month -->
<bpmn:timeDuration>P3M</bpmn:timeDuration> <!-- 3 months -->
<bpmn:timeDuration>P1Y</bpmn:timeDuration> <!-- 1 year -->

<!-- Complex durations -->
<bpmn:timeDuration>P1DT12H</bpmn:timeDuration> <!-- 1 day 12 hours -->
<bpmn:timeDuration>P2DT3H30M</bpmn:timeDuration> <!-- 2 days 3 hours 30 minutes -->

Duration Components:

  • P - Period designator (required)
  • Y - Years
  • M - Months (before T)
  • W - Weeks
  • D - Days
  • T - Time designator (separates date and time)
  • H - Hours
  • M - Minutes (after T)
  • S - Seconds

Use Cases:

  • User task timeouts
  • SLA monitoring
  • Approval deadlines
  • Payment grace periods

3. Time Cycle (Recurring Schedule)

Repeat at regular intervals:

<!-- Cron-like format: R/[duration] or cron expression -->

<!-- Repeat every hour -->
<bpmn:timeCycle>R/PT1H</bpmn:timeCycle>

<!-- Repeat every day at 2 AM (cron: minute hour day month dayOfWeek) -->
<bpmn:timeCycle>0 2 * * *</bpmn:timeCycle>

<!-- Repeat every Monday at 9 AM -->
<bpmn:timeCycle>0 9 * * 1</bpmn:timeCycle>

<!-- Repeat on 1st of every month at midnight -->
<bpmn:timeCycle>0 0 1 * *</bpmn:timeCycle>

<!-- Repeat every 15 minutes -->
<bpmn:timeCycle>R/PT15M</bpmn:timeCycle>

<!-- Repeat 3 times with 1 hour interval -->
<bpmn:timeCycle>R3/PT1H</bpmn:timeCycle>

Cron Format (minute hour day month dayOfWeek):

  • * - Any value
  • , - List of values (e.g., 1,3,5)
  • - - Range (e.g., 1-5)
  • / - Step (e.g., */15 = every 15)

Use Cases:

  • Daily/weekly/monthly reports
  • Batch processing jobs
  • Data synchronization
  • Cleanup tasks

Common Patterns

Pattern 1: Escalation Chain

Multiple reminders leading to escalation:

<bpmn:userTask id="TeamLeadApproval" name="Team Lead Approval">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="FormKey" value="approval-form"/>
<custom:property name="ResponsibleUsers" value="context.teamLeadEmail"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:userTask>

<!-- First reminder: 12 hours (non-interrupting) -->
<bpmn:boundaryEvent id="Reminder12h"
attachedToRef="TeamLeadApproval"
cancelActivity="false">
<bpmn:timerEventDefinition>
<bpmn:timeDuration>PT12H</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:boundaryEvent>

<bpmn:sendTask id="SendReminder12h" name="Send 12h Reminder">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="messageType" value="email"/>
<custom:property name="to" value="context.teamLeadEmail"/>
<custom:property name="subject" value="Reminder: Approval Pending (12 hours)"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:sendTask>

<!-- Second reminder: 24 hours (non-interrupting) -->
<bpmn:boundaryEvent id="Reminder24h"
attachedToRef="TeamLeadApproval"
cancelActivity="false">
<bpmn:timerEventDefinition>
<bpmn:timeDuration>P1D</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:boundaryEvent>

<bpmn:sendTask id="SendReminder24h" name="Send 24h Reminder">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="messageType" value="email"/>
<custom:property name="to" value="context.teamLeadEmail"/>
<custom:property name="subject" value="URGENT: Approval Pending (24 hours)"/>
<custom:property name="cc" value="context.managerEmail"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:sendTask>

<!-- Escalation: 48 hours (interrupting) -->
<bpmn:boundaryEvent id="Escalate48h"
attachedToRef="TeamLeadApproval"
cancelActivity="true">
<bpmn:timerEventDefinition>
<bpmn:timeDuration>P2D</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:boundaryEvent>

<!-- Escalate to manager -->
<bpmn:userTask id="ManagerApproval" name="Manager Approval (Escalated)">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="FormKey" value="approval-form"/>
<custom:property name="ResponsibleUsers" value="context.managerEmail"/>
<custom:property name="Description" value="ESCALATED: Team lead approval timed out after 48 hours"/>
<custom:property name="Priority" value="HIGH"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:userTask>

Pattern 2: Payment Grace Period

Allow time for payment with automatic expiry:

<bpmn:serviceTask id="GenerateInvoice" name="Generate Invoice"/>

<!-- Wait 30 days for payment -->
<bpmn:intermediateCatchEvent id="PaymentGracePeriod" name="30 Day Grace Period">
<bpmn:timerEventDefinition>
<bpmn:timeDuration>P30D</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:intermediateCatchEvent>

<!-- Check payment status after grace period -->
<bpmn:scriptTask id="CheckPaymentStatus" name="Check Payment Status">
<bpmn:script>
// Check if payment received
var paymentReceived = BankLingo.ExecuteCommand('CheckPaymentStatus', {
invoiceId: context.invoiceId
});

context.paymentReceived = paymentReceived.result;

return {
paymentReceived: paymentReceived.result
};
</bpmn:script>
</bpmn:scriptTask>

<!-- Gateway: Payment received? -->
<bpmn:exclusiveGateway id="PaymentReceived" name="Payment Received?"/>

<!-- Yes: Continue -->
<bpmn:sequenceFlow sourceRef="PaymentReceived" targetRef="ActivateService">
<bpmn:conditionExpression>${context.paymentReceived === true}</bpmn:conditionExpression>
</bpmn:sequenceFlow>

<!-- No: Send overdue notice -->
<bpmn:sequenceFlow sourceRef="PaymentReceived" targetRef="SendOverdueNotice">
<bpmn:conditionExpression>${context.paymentReceived === false}</bpmn:conditionExpression>
</bpmn:sequenceFlow>

Pattern 3: Scheduled Batch Processing

Daily batch job at specific time:

<!-- Start every day at 2 AM -->
<bpmn:startEvent id="DailyBatchStart" name="Daily at 2 AM">
<bpmn:timerEventDefinition>
<bpmn:timeCycle>0 2 * * *</bpmn:timeCycle>
</bpmn:timerEventDefinition>
</bpmn:startEvent>

<bpmn:scriptTask id="PrepareData" name="Prepare Batch Data">
<bpmn:script>
// Get yesterday's transactions
var yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);

var transactions = BankLingo.ExecuteCommand('GetTransactions', {
startDate: yesterday.toISOString().split('T')[0],
endDate: yesterday.toISOString().split('T')[0]
});

context.batchTransactions = transactions.result;
context.batchDate = yesterday.toISOString().split('T')[0];
context.transactionCount = transactions.result.length;

logger.info('Batch processing ' + context.transactionCount + ' transactions for ' + context.batchDate);

return {
transactionCount: context.transactionCount,
batchDate: context.batchDate
};
</bpmn:script>
</bpmn:scriptTask>

<!-- Process transactions in multi-instance -->
<bpmn:callActivity id="ProcessTransactions"
name="Process Transactions"
calledElement="ProcessSingleTransaction">
<bpmn:multiInstanceLoopCharacteristics isSequential="false">
<custom:property name="collection" value="context.batchTransactions"/>
<custom:property name="elementVariable" value="currentTransaction"/>
</bpmn:multiInstanceLoopCharacteristics>
</bpmn:callActivity>

<bpmn:scriptTask id="GenerateReport" name="Generate Batch Report">
<bpmn:script>
logger.info('Batch processing complete for ' + context.batchDate);

// Generate and send report
BankLingo.ExecuteCommand('GenerateBatchReport', {
batchDate: context.batchDate,
transactionCount: context.transactionCount
});
</bpmn:script>
</bpmn:scriptTask>

Pattern 4: SLA Monitoring (Non-Interrupting)

Track SLA violations without interrupting process:

<bpmn:userTask id="CustomerSupport" name="Handle Support Ticket">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="FormKey" value="support-ticket-form"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:userTask>

<!-- SLA Warning: 2 hours (non-interrupting) -->
<bpmn:boundaryEvent id="SLAWarning"
attachedToRef="CustomerSupport"
cancelActivity="false">
<bpmn:timerEventDefinition>
<bpmn:timeDuration>PT2H</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:boundaryEvent>

<bpmn:scriptTask id="LogSLAWarning" name="Log SLA Warning">
<bpmn:script>
logger.warn('SLA Warning: Support ticket ' + context.ticketId + ' approaching 2 hour limit');

context.slaWarningTriggered = true;
context.slaWarningTime = new Date().toISOString();

// Send alert to supervisor
BankLingo.ExecuteCommand('SendAlert', {
severity: 'MEDIUM',
message: 'Ticket ' + context.ticketId + ' approaching SLA limit',
assignedTo: context.assignedAgent
});
</bpmn:script>
</bpmn:scriptTask>

<!-- SLA Breach: 4 hours (non-interrupting) -->
<bpmn:boundaryEvent id="SLABreach"
attachedToRef="CustomerSupport"
cancelActivity="false">
<bpmn:timerEventDefinition>
<bpmn:timeDuration>PT4H</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:boundaryEvent>

<bpmn:scriptTask id="LogSLABreach" name="Log SLA Breach">
<bpmn:script>
logger.error('SLA BREACH: Support ticket ' + context.ticketId + ' exceeded 4 hour limit');

context.slaBreached = true;
context.slaBreachTime = new Date().toISOString();

// Send critical alert
BankLingo.ExecuteCommand('SendAlert', {
severity: 'HIGH',
message: 'BREACH: Ticket ' + context.ticketId + ' exceeded SLA',
escalateTo: context.supervisorEmail
});
</bpmn:script>
</bpmn:scriptTask>

Pattern 5: Delayed Retry After Error

Retry failed operation after delay:

<bpmn:serviceTask id="CallExternalAPI" name="Call External API">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="ConnectorKey" value="CallPartnerAPICommand"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:serviceTask>

<!-- Catch retryable errors -->
<bpmn:boundaryEvent id="APIError"
attachedToRef="CallExternalAPI"
cancelActivity="true">
<bpmn:errorEventDefinition errorRef="RetryableError" />
</bpmn:boundaryEvent>

<!-- Calculate retry delay -->
<bpmn:scriptTask id="CalculateRetryDelay" name="Calculate Retry Delay">
<bpmn:script>
var retryCount = (context.apiRetryCount || 0) + 1;
var maxRetries = 3;

if (retryCount > maxRetries) {
throw new BpmnError('API_RETRY_EXHAUSTED',
'Failed after ' + maxRetries + ' attempts');
}

context.apiRetryCount = retryCount;

// Exponential backoff: 2^n seconds
context.retryDelaySeconds = Math.pow(2, retryCount);

logger.info('Retry ' + retryCount + ' after ' + context.retryDelaySeconds + ' seconds');

return {
retryCount: retryCount,
delaySeconds: context.retryDelaySeconds
};
</bpmn:script>
</bpmn:scriptTask>

<!-- Wait before retry -->
<bpmn:intermediateCatchEvent id="WaitBeforeRetry" name="Wait">
<bpmn:timerEventDefinition>
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">
PT${context.retryDelaySeconds}S
</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:intermediateCatchEvent>

<!-- Loop back to retry -->
<bpmn:sequenceFlow sourceRef="WaitBeforeRetry" targetRef="CallExternalAPI" />

<bpmn:error id="RetryableError" errorCode="GATEWAY_TIMEOUT" />

Dynamic Timer Durations

Use context variables for dynamic timer durations:

<!-- Dynamic timeout based on transaction amount -->
<bpmn:scriptTask id="SetTimeout" name="Set Timeout">
<bpmn:script>
// Higher amounts get longer approval time
if (context.amount > 100000) {
context.approvalTimeoutDays = 7;
} else if (context.amount > 50000) {
context.approvalTimeoutDays = 3;
} else {
context.approvalTimeoutDays = 1;
}

logger.info('Approval timeout: ' + context.approvalTimeoutDays + ' days');
</bpmn:script>
</bpmn:scriptTask>

<bpmn:userTask id="Approval" name="Approval"/>

<!-- Dynamic timeout -->
<bpmn:boundaryEvent id="ApprovalTimeout"
attachedToRef="Approval"
cancelActivity="true">
<bpmn:timerEventDefinition>
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">
P${context.approvalTimeoutDays}D
</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:boundaryEvent>

Timer Persistence

All timers are persisted in the ProcessTimer database table:

Code Removed

Implementation details removed for security.

Contact support for implementation guidance.

Timer Processing:

  • Background job checks for due timers every minute
  • When timer fires, process is resumed
  • Repeating timers (cycle) create new timer entries
  • Completed timers are marked as processed

Best Practices

✅ Do This

<!-- ✅ Use clear timer names -->
<bpmn:intermediateCatchEvent id="Wait3Days" name="Wait 3 Days">
<bpmn:timerEventDefinition>
<bpmn:timeDuration>P3D</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:intermediateCatchEvent>

<!-- ✅ Use non-interrupting for reminders -->
<bpmn:boundaryEvent cancelActivity="false">

<!-- ✅ Use interrupting for hard deadlines -->
<bpmn:boundaryEvent cancelActivity="true">

<!-- ✅ Add escalation chain -->
<!-- Reminder at 12h, 24h, then escalate at 48h -->

<!-- ✅ Use dynamic timeouts based on context -->
<bpmn:timeDuration>P${context.timeoutDays}D</bpmn:timeDuration>

<!-- ✅ Log timer events -->
logger.info('Timer started: ' + timerType + ' - ' + duration);

❌ Don't Do This

<!-- ❌ Very short timers (< 1 minute) -->
<bpmn:timeDuration>PT10S</bpmn:timeDuration> <!-- Use sync logic instead -->

<!-- ❌ Very long timers (> 1 year) -->
<bpmn:timeDuration>P5Y</bpmn:timeDuration> <!-- Reconsider process design -->

<!-- ❌ Interrupting reminders -->
<bpmn:boundaryEvent cancelActivity="true"> <!-- Should be false for reminders -->

<!-- ❌ No timeout on user tasks -->
<!-- User task with no timeout - could wait forever -->

<!-- ❌ Incorrect ISO 8601 format -->
<bpmn:timeDuration>1 day</bpmn:timeDuration> <!-- Should be P1D -->

Features Used:

  • Phase 4: Timer Events

Status: ✅ Production Ready
Version: 2.0
Last Updated: January 2026