Skip to main content
Signals provide a way to send asynchronous messages to running workflows from external systems. They enable workflows to respond to external events and change their behavior dynamically.

Overview

Signals are methods on your workflow class marked with the #[SignalMethod] attribute. They allow external code to communicate with a running workflow without waiting for a response.

Defining Signal Methods

Mark methods with the #[SignalMethod] attribute to make them callable from outside the workflow:
use Workflow\Workflow;
use Workflow\SignalMethod;

class OrderWorkflow extends Workflow
{
    private bool $canceled = false;
    private string $status = 'pending';
    
    #[SignalMethod]
    public function cancel(): void
    {
        $this->canceled = true;
    }
    
    #[SignalMethod]
    public function updateStatus(string $status): void
    {
        $this->status = $status;
    }
    
    public function execute()
    {
        yield await(fn () => $this->canceled || $this->status === 'approved');
        
        if ($this->canceled) {
            return 'Order canceled';
        }
        
        return 'Order processed';
    }
}
Signal methods must return void. They cannot return values to the caller.

Sending Signals

Send signals to a workflow using the workflow instance:
use Workflow\WorkflowStub;

// Get the workflow instance
$workflow = WorkflowStub::find($workflowId);

// Send a signal
$workflow->cancel();

// Send a signal with parameters
$workflow->updateStatus('processing');

Signal Characteristics

Asynchronous

Signals return immediately without waiting for the workflow to process them

Persistent

Signals are queued and persisted, ensuring delivery even if the workflow is paused

Ordered

Signals are processed in the order they are received

Void Return

Signal methods cannot return values to the caller

Using Signals with Await

A common pattern is to use signals to modify workflow state that is being awaited:
use function Workflow\await;

class ApprovalWorkflow extends Workflow
{
    private bool $approved = false;
    private bool $rejected = false;
    
    #[SignalMethod]
    public function approve(): void
    {
        $this->approved = true;
    }
    
    #[SignalMethod]
    public function reject(): void
    {
        $this->rejected = true;
    }
    
    public function execute()
    {
        // Wait for either approval or rejection
        yield await(fn () => $this->approved || $this->rejected);
        
        if ($this->approved) {
            yield activity(ProcessApprovalActivity::class);
            return 'approved';
        }
        
        return 'rejected';
    }
}

Multiple Signals

Workflows can have multiple signal methods for different purposes:
class OrderProcessingWorkflow extends Workflow
{
    private bool $canceled = false;
    private bool $expedited = false;
    private ?string $shippingAddress = null;
    
    #[SignalMethod]
    public function cancel(): void
    {
        $this->canceled = true;
    }
    
    #[SignalMethod]
    public function expedite(): void
    {
        $this->expedited = true;
    }
    
    #[SignalMethod]
    public function updateShippingAddress(string $address): void
    {
        $this->shippingAddress = $address;
    }
    
    public function execute($orderId)
    {
        // Process order with signal-based modifications
        yield timer(3600); // 1 hour processing time
        
        if ($this->canceled) {
            return 'canceled';
        }
        
        $address = $this->shippingAddress ?? 'default address';
        $priority = $this->expedited ? 'high' : 'normal';
        
        yield activity(ShipOrderActivity::class, $orderId, $address, $priority);
        
        return 'shipped';
    }
}

Webhook Integration

Signals can be exposed as webhooks for external systems to trigger:
use Workflow\Webhook;

class SubscriptionWorkflow extends Workflow
{
    private bool $paymentReceived = false;
    
    #[SignalMethod]
    #[Webhook]
    public function handlePayment(array $paymentData): void
    {
        $this->paymentReceived = true;
    }
    
    public function execute()
    {
        // Wait for payment webhook
        yield await(fn () => $this->paymentReceived);
        
        yield activity(ActivateSubscriptionActivity::class);
        
        return 'activated';
    }
}
When using the #[Webhook] attribute, the signal can be triggered via HTTP POST to the workflow’s webhook URL.

Signal Replay Safety

Signals are deterministic and replay-safe. The workflow execution history ensures that signals are processed consistently during replays:
public function execute()
{
    $count = 0;
    
    // This will be replayed deterministically
    yield await(function () use (&$count) {
        // State modified by signals is preserved during replay
        return $this->shouldContinue;
    });
    
    return 'completed';
}

Use Cases

Human Intervention

Allow humans to approve, reject, or modify workflow execution

External Events

React to external system events via webhooks

Cancellation

Provide a mechanism to cancel long-running workflows

Dynamic Updates

Update workflow parameters while execution is in progress

Best Practices

Signal methods should only update workflow state. Avoid complex logic or blocking operations.
Signal method names should clearly describe their purpose (e.g., cancel(), approve(), updateAddress()).
Always validate parameters passed to signal methods to prevent invalid state.
Clearly document what each signal does and when it should be used.

Build docs developers (and LLMs) love