Blocking Activities & Triggers
This guide explains how to implement custom blocking activities and triggers in Elsa Workflows v3. Blocking activities use bookmarks to pause workflow execution and wait for external events, while triggers start or resume workflows in response to external stimuli.
Table of Contents
Overview
Elsa Workflows supports two primary patterns for coordinating with external systems:
Blocking Activities (Bookmarks): Activities that pause workflow execution and create a bookmark that can be resumed later by an external event
Trigger Activities: Activities that start or resume workflows when specific events occur
Both patterns use Elsa's bookmark system under the hood. The key difference is in their usage:
Blocking activities are placed inline in a workflow and pause execution at that point
Triggers are typically placed at the start of a workflow and wait for specific events to start or resume execution
Bookmarks and Resume Flows
What is a Bookmark?
A bookmark is Elsa's mechanism for pausing a workflow and storing its state until an external event occurs. When a workflow creates a bookmark:
The workflow execution pauses at the current activity
A bookmark record is persisted to the database
The workflow instance enters a suspended state
External code can resume the workflow by providing the bookmark information
Bookmark Lifecycle
Bookmark Correlation
Bookmarks use a hash-based correlation mechanism to match external events to the correct workflow instance. When creating a bookmark, you provide:
Bookmark Name: A logical identifier (e.g., "WaitForApproval")
Payload: Optional data used to calculate the bookmark hash
Correlation ID: Optional workflow-level correlation for multi-instance scenarios
The bookmark hash is calculated from these values and is used to locate the correct bookmark when resuming.
When to Use Blocking Activities
Use blocking activities when your workflow needs to:
Wait for human interaction: Approvals, form submissions, manual reviews
Coordinate with external systems: Wait for callbacks, webhooks, or async operations
Implement timeouts: Combine with timers to handle time-sensitive operations
Handle long-running operations: Operations that may take hours, days, or weeks
Common Use Cases
Human approvals
Expense approval, document review
WaitForApproval activity
External callbacks
Payment gateway, third-party API
Webhook receiver
Scheduled operations
Wait until specific date/time
Timer + bookmark
Fan-in scenarios
Wait for multiple signals
Trigger with aggregation
Creating a Blocking Activity
Let's create a complete example of a blocking activity that waits for an approval decision.
Step 1: Define the Activity
See the complete implementation in WaitForApprovalActivity.cs.
Step 2: Register the Activity
In your Program.cs or startup configuration:
Key Concepts
CreateBookmark vs CreateBookmarkArgs
Elsa provides multiple ways to create bookmarks:
ActivityExecutionContext APIs
The ActivityExecutionContext provides several key methods:
CreateBookmark(CreateBookmarkArgs): Creates a bookmark with detailed configurationCreateBookmark(string, Func<ActivityExecutionContext, ValueTask>): Creates a simple bookmarkGenerateBookmarkTriggerUrl(string bookmarkId): Generates a tokenized HTTP URL for resuming (requires Elsa.Http)CompleteActivityWithOutcomesAsync(params string[]): Completes the activity with specific outcomesSet<T>(Output<T>, T): Sets an output valueGet<T>(Input<T>): Gets an input value
Resuming Workflows
There are multiple patterns for resuming workflows from external code.
Pattern 1: Resume by Bookmark Stimulus
This pattern uses a "stimulus" - a payload containing the bookmark name and correlation data. Elsa will find all matching bookmarks and resume them.
Pattern 2: Resume by Bookmark ID
This pattern directly targets a specific bookmark using its ID. This is more precise but requires storing the bookmark ID.
Pattern 3: Resume via HTTP Trigger URL
When using GenerateBookmarkTriggerUrl, Elsa automatically creates an HTTP endpoint that can resume the workflow:
The token contains encrypted bookmark information, so you don't need to manually specify the bookmark ID or stimulus.
See complete controller examples in ApprovalController.cs.
Creating Trigger Activities
Triggers are special activities that can start or resume workflows based on external events. They inherit from the Trigger base class and implement payload generation.
Example: SignalFanIn Trigger
This example shows a trigger that waits for multiple signals before continuing:
See the complete implementation in SignalFanInTrigger.cs.
Trigger Indexing
Elsa uses trigger indexing to efficiently match incoming events to workflows. When a trigger activity is registered:
GetTriggerPayloadsis called during indexingThe returned payloads are hashed and stored in the trigger index
When an event occurs, Elsa computes a hash and looks up matching workflows
Matching workflows are started or resumed
This allows Elsa to quickly find relevant workflows without scanning all workflow definitions.
Best Practices
1. Correlation and Idempotency
Always design your bookmarks and triggers with correlation in mind:
Idempotency: Ensure that resuming the same bookmark multiple times with the same input doesn't cause issues. Use the AutoBurn = true setting to consume bookmarks after one use.
2. Timeouts and Fallback Paths
Always provide timeout handling for blocking activities:
3. Distributed Locking
Elsa's WorkflowResumer automatically handles distributed locking when resuming workflows. This ensures that:
Multiple resume requests for the same bookmark don't cause race conditions
Workflows execute safely in clustered/multi-instance deployments
Bookmark consumption is atomic
You don't need to implement your own locking logic - Elsa handles this internally using IDistributedLockProvider.
4. Scheduled Bookmarks and Timers
For time-based operations, use Elsa's built-in timer activities or scheduled bookmarks:
The DefaultBookmarkScheduler handles scheduled bookmarks using background jobs.
Timezone Considerations:
Store times in UTC to avoid timezone issues
Use
DateTime.UtcNowinstead ofDateTime.NowWhen displaying times to users, convert to their local timezone
Single-Instance vs Clustered:
In single-instance deployments, the scheduler runs in the same process
In clustered deployments, use a distributed scheduler (e.g., Quartz.NET with shared storage)
Ensure only one instance processes each scheduled bookmark
5. Bookmark Retention and Cleanup
Configure bookmark retention policies to prevent database growth:
6. Error Handling and Fault Tolerance
Handle failures gracefully in your resume callbacks:
7. Testing Blocking Activities
Test your blocking activities thoroughly:
Troubleshooting
Common Issues and Solutions
1. Bookmark Not Found When Resuming
Symptom: ResumeAsync returns no results or null.
Possible Causes:
Bookmark payload hash doesn't match
Bookmark already consumed (AutoBurn = true)
Workflow instance deleted or expired
Solutions:
Verify the payload data matches exactly what was used during bookmark creation
Check the
AutoBurnsetting - set tofalseif the bookmark should be reusableEnsure the workflow instance still exists in the database
Use bookmark ID-based resume for exact matching
2. GenerateBookmarkTriggerUrl Throws Exception
Symptom: Exception when calling GenerateBookmarkTriggerUrl.
Possible Causes:
Elsa.Http module not installed or configured
Base URL not configured
Solutions:
3. Workflow Not Resuming in Clustered Deployment
Symptom: Workflows don't resume in multi-instance deployments.
Possible Causes:
Distributed locking not configured
Database not shared between instances
Trigger indexing not synchronized
Solutions:
4. Trigger Not Starting Workflow
Symptom: Trigger activity registered but workflow doesn't start.
Possible Causes:
Trigger not properly indexed
Payload hash mismatch
Workflow not published
Solutions:
Ensure
GetTriggerPayloadsreturns consistent payload structuresVerify the workflow is published (not just saved as draft)
Check trigger indexing logs for errors
Rebuild the trigger index if necessary
5. Memory Leaks with Long-Running Workflows
Symptom: Memory usage grows over time with many suspended workflows.
Solutions:
Configure retention policies to clean up old workflows
Use external storage for large workflow data
Implement bookmark expiration logic:
Debugging Checklist
When troubleshooting blocking activities and triggers:
Diagnostic Queries
Useful SQL queries for troubleshooting (adjust table names for your database):
Additional Resources
Example Files
WaitForApprovalActivity.cs - Complete blocking activity implementation
ApprovalController.cs - Controller showing resume patterns
SignalFanInTrigger.cs - Trigger example with fan-in pattern
workflow-wait-for-approval.json - Sample workflow JSON
This guide covers the core concepts and patterns for implementing blocking activities and triggers in Elsa v3. For more advanced scenarios, consult the Elsa source code at github.com/elsa-workflows/elsa-core.
Last updated