ArchitectureNodes

Trigger Node

Trigger nodes are the entry points for workflows in Xentom. They listen for external events and initiate workflow execution when those events occur. Trigger nodes can invoke other nodes but cannot be invoked themselves, making them the starting point of any automation flow.

Key Characteristics

  • Workflow Entry Points: Only trigger nodes can start a workflow
  • Event-Driven: Respond to external events like webhooks, timers, or system events
  • Cannot be Invoked: Other nodes cannot call trigger nodes directly
  • Can Invoke Others: Trigger nodes can call callable nodes via execution pins
  • Subscribe Pattern: Use a subscription model to listen for events

Basic Structure

import * as i from '@xentom/integration-framework';

export const onWebhook = i.nodes.trigger({
  // Optional: group your node in the UI
  group: 'External/HTTP',

  // Optional: custom display name
  displayName: 'Webhook Receiver',

  // Optional: description for users and AI assistance
  description: 'Receives HTTP requests and processes the payload',

  // Configuration inputs from the user
  inputs: {
    path: i.pins.data({
      control: i.controls.text({
        label: 'Webhook Path',
        placeholder: '/webhook',
        defaultValue: '/webhook',
      }),
    }),
  },

  // Data outputs when triggered
  outputs: {
    payload: i.pins.data({
      displayName: 'Request Payload',
      description: 'The parsed request body',
    }),
    headers: i.pins.data({
      displayName: 'HTTP Headers',
      description: 'Request headers as key-value pairs',
    }),
  },

  // Subscribe function: sets up event listeners
  subscribe({ next, webhook, inputs, state, variables }) {
    // Set up event listening logic
    const unsubscribe = webhook.subscribe(async (req) => {
      try {
        const payload = await req.json();

        // Emit outputs and start workflow
        next({
          payload,
          headers: Object.fromEntries(req.headers),
        });

        return new Response('OK', { status: 200 });
      } catch (error) {
        return new Response('Bad Request', { status: 400 });
      }
    });

    // Always return cleanup function
    return () => unsubscribe();
  },
});

Configuration Options

Prop

Type

The Subscribe Function

The subscribe function is the heart of a trigger node. It sets up event listeners and returns a cleanup function:

Parameters

The subscribe function receives an options object with:

Prop

Type

Return Value

The subscribe function must return a cleanup function (or a promise that resolves to one) that will be called when the workflow stops or reconfigures.

Common Trigger Patterns

Webhook Triggers

Handle incoming HTTP requests:

export const onWebhook = i.nodes.trigger({
  inputs: {
    secretKey: i.pins.data({
      control: i.controls.text({
        label: 'Webhook Secret',
        description: 'Secret key for webhook verification',
        sensitive: true,
      }),
    }),
  },

  outputs: {
    verified: i.pins.exec({
      outputs: {
        payload: i.pins.data(),
        signature: i.pins.data(),
      },
    }),
    invalid: i.pins.exec(),
  },

  subscribe({ next, webhook, inputs }) {
    const unsubscribe = webhook.subscribe(async (req) => {
      try {
        const signature = req.headers.get('X-Signature');
        const payload = await req.text();

        if (!verifySignature(payload, signature, inputs.secretKey)) {
          next('invalid');
          return new Response('Unauthorized', { status: 401 });
        }

        const data = JSON.parse(payload);
        next('verified', { payload: data, signature });

        return new Response('OK');
      } catch (error) {
        next('invalid');
        return new Response('Bad Request', { status: 400 });
      }
    });

    return () => unsubscribe();
  },
});

Timer Triggers

Execute workflows on a schedule:

export const timerTrigger = i.nodes.trigger({
  inputs: {
    interval: i.pins.data({
      control: i.controls.select({
        label: 'Interval',
        options: [
          { value: 30, label: '30 seconds' },
          { value: 60, label: '1 minute' },
          { value: 300, label: '5 minutes' },
          { value: 3600, label: '1 hour' },
        ],
        defaultValue: 60,
      }),
    }),
  },

  outputs: {
    timestamp: i.pins.data({
      displayName: 'Timestamp',
      description: 'Current timestamp when triggered',
    }),
  },

  subscribe({ next, inputs }) {
    const intervalMs = inputs.interval * 1000;

    const timer = setInterval(() => {
      next({
        timestamp: new Date().toISOString(),
      });
    }, intervalMs);

    return () => clearInterval(timer);
  },
});

External Event Triggers

Listen to external services or databases:

export const databaseTrigger = i.nodes.trigger({
  inputs: {
    tableName: i.pins.data({
      control: i.controls.text({
        label: 'Table Name',
        placeholder: 'users',
      }),
    }),
  },

  outputs: {
    newRecord: i.pins.data({
      displayName: 'New Record',
      description: 'The newly created database record',
    }),
  },

  subscribe({ next, inputs, state }) {
    // Listen for database changes
    const listener = state.database.listen(inputs.tableName, (change) => {
      if (change.type === 'INSERT') {
        next({
          newRecord: change.record,
        });
      }
    });

    return () => listener.stop();
  },
});

File System Triggers

Monitor file system changes:

export const fileWatcher = i.nodes.trigger({
  inputs: {
    directory: i.pins.data({
      control: i.controls.text({
        label: 'Watch Directory',
        placeholder: '/uploads',
      }),
    }),
    pattern: i.pins.data({
      control: i.controls.text({
        label: 'File Pattern',
        placeholder: '*.csv',
        defaultValue: '*',
      }),
    }),
  },

  outputs: {
    fileCreated: i.pins.exec({
      outputs: {
        filePath: i.pins.data(),
        fileName: i.pins.data(),
        size: i.pins.data(),
      },
    }),
    fileDeleted: i.pins.exec({
      outputs: {
        filePath: i.pins.data(),
      },
    }),
  },

  subscribe({ next, inputs, state }) {
    const watcher = state.fileSystem.watch(inputs.directory, {
      pattern: inputs.pattern,
    });

    watcher.on('created', (file) => {
      next('fileCreated', {
        filePath: file.path,
        fileName: file.name,
        size: file.size,
      });
    });

    watcher.on('deleted', (file) => {
      next('fileDeleted', {
        filePath: file.path,
      });
    });

    return () => watcher.close();
  },
});

Execution Flow Control

Trigger nodes can use execution pins to create conditional or parallel execution paths:

export const conditionalTrigger = i.nodes.trigger({
  outputs: {
    businessHours: i.pins.exec({
      outputs: {
        message: i.pins.data(),
      },
    }),
    afterHours: i.pins.exec({
      outputs: {
        message: i.pins.data(),
      },
    }),
  },

  subscribe({ next }) {
    const timer = setInterval(() => {
      const hour = new Date().getHours();

      if (hour >= 9 && hour < 17) {
        next('businessHours', {
          message: 'Business hours workflow',
        });
      } else {
        next('afterHours', {
          message: 'After hours workflow',
        });
      }
    }, 60000);

    return () => clearInterval(timer);
  },
});

State Management

Trigger nodes can access and modify the shared integration state:

export const statefulTrigger = i.nodes.trigger({
  outputs: {
    event: i.pins.data(),
  },

  subscribe({ next, state }) {
    // Initialize state if needed
    if (!state.eventCounter) {
      state.eventCounter = 0;
    }

    const timer = setInterval(() => {
      state.eventCounter++;

      next({
        event: {
          id: state.eventCounter,
          timestamp: new Date().toISOString(),
        },
      });
    }, 5000);

    return () => clearInterval(timer);
  },
});

Best Practices

Always Provide Cleanup

// Good - Always return cleanup function
subscribe({ next }) {
  const timer = setInterval(() => next({}), 1000);
  return () => clearInterval(timer);
}

// Bad - No cleanup function
subscribe({ next }) {
  setInterval(() => next({}), 1000);
  // Memory leak! Timer continues after workflow stops
}

Handle Async Cleanup

subscribe({ next, state }) {
  state.connection.listen('event', next);

  return async () => {
    await state.connection.disconnect();
  };
}

Validate Inputs

subscribe({ next, inputs }) {
  if (!inputs.apiKey) {
    throw new Error('API key is required');
  }

  // Continue with setup...
}

Use Descriptive Outputs

outputs: {
  // Good - Clear and descriptive
  userRegistered: i.pins.exec({
    outputs: {
      userId: i.pins.data(),
      email: i.pins.data(),
      registrationTime: i.pins.data()
    }
  }),

  // Avoid - Generic names
  event: i.pins.exec({
    outputs: {
      data: i.pins.data()
    }
  })
}

Trigger nodes are the foundation of workflow automation in Xentom, providing the bridge between external events and your business logic. By following these patterns and best practices, you can create robust, reliable triggers that power your automations.

On this page