Skip to content

Signal Wait Nodes

Signal wait nodes pause workflow execution until an external signal is received. They're the foundation for async operations and external integrations.

Definition

dart
builder.signalWait(
  'awaitPayment',
  name: 'Await Payment Confirmation',
  signal: 'payment_completed',
  storeAs: 'paymentResult',
);

Signal Wait Properties

PropertyDescription
signalName of signal to wait for
storeAsKey for storing signal payload in output
nameHuman-readable description

How It Works

Step Details:

  1. Token arrives at SignalWait node
  2. SignalWaitNodeExecutor returns WaitForSignalResult with signalName and storeAs path
  3. Engine updates status to waitingForSignal, token stays at current node, instance is persisted
  4. External system sends signal (seconds, minutes, or days later)
  5. engine.sendSignal() finds instance, matches signal, calls executor.onSignalReceived()
  6. Payload stored at output[storeAs]
  7. Executor returns ContinueResult, workflow continues

Sending Signals

dart
await engine.sendSignal(
  workflowInstanceId: instanceId,
  node: 'awaitPayment',  // The node ID waiting for the signal
  payload: {
    'transactionId': 'TXN-123456',
    'amount': 99.99,
    'currency': 'USD',
    'paidAt': DateTime.now().toIso8601String(),
  },
);

Use Cases

External Webhooks

dart
// Workflow waits for payment
builder.signalWait('awaitPayment',
  signal: 'stripe_payment_success',
  storeAs: 'paymentData',
);

// POST /webhooks/stripe
Future<void> handleStripeWebhook(StripeEvent event) async {
  if (event.type == 'payment_intent.succeeded') {
    final instanceId = event.metadata['workflowInstanceId'];
    await engine.sendSignal(
      workflowInstanceId: instanceId,
      node: 'awaitPayment',  // The node ID
      payload: event.data,
    );
  }
}

Email Verification

dart
builder.signalWait('awaitEmailVerification',
  signal: 'email_verified',
  storeAs: 'verification',
);

// GET /verify/:token
Future<void> verifyEmail(String token) async {
  final data = decodeVerificationToken(token);
  await engine.sendSignal(
    workflowInstanceId: data.workflowInstanceId,
    node: 'awaitEmailVerification',  // The node ID
    payload: {'verifiedAt': DateTime.now().toIso8601String()},
  );
}

Document Upload

dart
builder.signalWait('awaitDocuments',
  signal: 'documents_uploaded',
  storeAs: 'documents',
);

// In file upload handler
Future<void> handleUpload(List<File> files, String instanceId) async {
  final uploadedFiles = await fileService.upload(files);
  await engine.sendSignal(
    workflowInstanceId: instanceId,
    node: 'awaitDocuments',  // The node ID
    payload: {
      'files': uploadedFiles.map((f) => f.toJson()).toList(),
      'uploadedAt': DateTime.now().toIso8601String(),
    },
  );
}

Timer Events

dart
builder.signalWait('awaitDeadline',
  signal: 'deadline_reached',
  storeAs: 'deadlineResult',
);

// Scheduled job sends signal when deadline is reached
class DeadlineJob {
  Future<void> checkDeadlines() async {
    final expiredInstances = await findExpiredInstances();
    for (final instance in expiredInstances) {
      await engine.sendSignal(
        workflowInstanceId: instance.id,
        node: 'awaitDeadline',  // The node ID
        payload: {'expired': true, 'expiredAt': DateTime.now().toIso8601String()},
      );
    }
  }
}

Signal vs User Task

AspectSignal WaitUser Task
Creates inbox itemNoYes
AssignmentN/ARole or user
UI integrationCustomStandard inbox
Best forExternal systems, async opsHuman decisions

Routing After Signal

Often combined with gateways:

dart
builder
    .signalWait('awaitDecision', signal: 'decision', storeAs: 'result')
    .oneOf('routeDecision', [
      Branch.whenFn(
        (o) => o['result']?['approved'] == true,
        then: 'processApproved',
      ),
      Branch.otherwise(then: 'processRejected'),
    ])
    .connect('awaitDecision', 'routeDecision');

Best Practices

  1. Use descriptive signal names - order_payment_received not signal1
  2. Always use storeAs - Namespace the payload
  3. Include timestamps - In payload for audit
  4. Validate payload - In downstream tasks
  5. Handle missing signals - Timeout patterns

Next Steps