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- YearsM- Months (before T)W- WeeksD- DaysT- Time designator (separates date and time)H- HoursM- 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:
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 -->
Related Documentation
- ISO 8601 Timer Formats - Complete format reference
- Scheduled Tasks - Batch job patterns
- User Task Timeouts - User task timeout patterns
- Receive Task Timeouts - External signal timeouts
- Error Recovery - Retry patterns with timers
Features Used:
- Phase 4: Timer Events
Status: ✅ Production Ready
Version: 2.0
Last Updated: January 2026