Skip to main content
A trigger controls when and how an integration flow runs. Triggers are defined with trigger() or pollingTrigger() and collected in the triggers field of component().

TriggerDefinition type

interface TriggerDefinition<
  TInputs extends Inputs,
  TConfigVars extends ConfigVarResultCollection,
  TAllowsBranching extends boolean,
  TResult extends TriggerResult<TAllowsBranching, TriggerPayload>,
> {
  /** How this trigger appears in the Prismatic UI. */
  display: ActionDisplayDefinition;
  /** Function invoked when the trigger fires. */
  perform: TriggerPerformFunction<TInputs, TConfigVars, TAllowsBranching, TResult>;
  /** Optional: run when a flow's instance is deployed. */
  onInstanceDeploy?: TriggerEventFunction<TInputs, TConfigVars>;
  /** Optional: run when a flow's instance is deleted. */
  onInstanceDelete?: TriggerEventFunction<TInputs, TConfigVars>;
  /** Optional: webhook lifecycle handlers (create/delete). */
  webhookLifecycleHandlers?: {
    create: TriggerEventFunction<TInputs, TConfigVars>;
    delete: TriggerEventFunction<TInputs, TConfigVars>;
  };
  /** Input fields presented to integration builders. */
  inputs: TInputs;
  /** Whether this trigger supports scheduled (recurring) execution. */
  scheduleSupport: "invalid" | "valid" | "required";
  /** Whether this trigger supports synchronous webhook responses. */
  synchronousResponseSupport: "invalid" | "valid" | "required";
  /** If true, stops flow execution after the trigger completes. */
  terminateExecution?: boolean;
  /** If true, breaks out of an enclosing loop. */
  breakLoop?: boolean;
  /** If true, allows conditional branching. */
  allowsBranching?: TAllowsBranching;
  /** Fixed branch names (when allowsBranching is true). */
  staticBranchNames?: string[];
  /** Input key whose value is the dynamic branch name at runtime. */
  dynamicBranchInput?: string;
  /** Example output payload, shown in the UI. */
  examplePayload?: Awaited<ReturnType<this["perform"]>>;
  /** @internal Only configurable by Prismatic for built-in triggers. */
  isCommonTrigger?: boolean;
}

scheduleSupport and synchronousResponseSupport

Both fields accept one of three string values:
ValueMeaning
"invalid"This feature is not supported and cannot be enabled
"valid"This feature is supported and can optionally be enabled
"required"This feature must be enabled for this trigger to work

TriggerResult shape

The perform function of a trigger must return a TriggerResult:
interface TriggerBaseResult<TPayload extends TriggerPayload> {
  /** The inbound request payload, passed to subsequent steps. */
  payload: TPayload;
  /** HTTP response sent back to the caller for synchronous invocations. */
  response?: HttpResponse;
  /** Data to persist in flow-specific instance state. */
  instanceState?: Record<string, unknown>;
  /** Data to persist across all flows. */
  crossFlowState?: Record<string, unknown>;
  /** Data available for the duration of this execution only. */
  executionState?: Record<string, unknown>;
  /** Data shared across all flows and versions of this integration. */
  integrationState?: Record<string, unknown>;
  /** Set by the platform to indicate failure. */
  failed?: boolean;
  /** Set by the platform with error details on failure. */
  error?: Record<string, unknown>;
  /** Set to true to indicate there were no new changes to process (polling). */
  polledNoChanges?: boolean;
}

// When allowsBranching: true, the result must also include:
interface TriggerBranchingResult<TPayload> extends TriggerBaseResult<TPayload> {
  branch: string;
}

Webhook trigger example

A webhook trigger receives an inbound HTTP request and passes its payload to subsequent steps:
import { trigger, input } from "@prismatic-io/spectral";

export const webhookTrigger = trigger({
  display: {
    label: "Webhook",
    description: "Trigger a flow when an HTTP request is received",
  },
  inputs: {
    signatureSecret: input({
      label: "Webhook Signature Secret",
      type: "password",
      required: false,
      comments: "Optional secret used to verify the webhook signature",
    }),
  },
  scheduleSupport: "invalid",
  synchronousResponseSupport: "valid",
  perform: async (context, payload, params) => {
    // Optionally verify the signature
    if (params.signatureSecret) {
      const signature = payload.headers["x-signature"] as string;
      verifySignature(payload.rawBody.data as Buffer, signature, params.signatureSecret);
    }

    context.logger.info("Webhook received", {
      method: payload.headers[":method"],
      contentType: payload.headers["content-type"],
    });

    return {
      payload,
      response: {
        statusCode: 200,
        contentType: "application/json",
        body: JSON.stringify({ received: true }),
      },
    };
  },
});

Scheduled trigger example

A scheduled trigger fires on a recurring schedule (cron) configured by the integration builder:
import { trigger } from "@prismatic-io/spectral";

export const scheduledTrigger = trigger({
  display: {
    label: "Schedule",
    description: "Run the flow on a recurring schedule",
  },
  inputs: {},
  scheduleSupport: "required",
  synchronousResponseSupport: "invalid",
  perform: async (context, payload, params) => {
    context.logger.info("Scheduled trigger fired", { startedAt: context.startedAt });
    return { payload };
  },
});

Instance lifecycle handlers

onInstanceDeploy and onInstanceDelete run when an instance using this trigger is deployed or deleted. These are useful for registering and deregistering webhooks with a third-party service:
import { trigger, input } from "@prismatic-io/spectral";

export const managedWebhookTrigger = trigger({
  display: {
    label: "Managed Webhook",
    description: "Automatically registers and deregisters a webhook with the external service",
  },
  inputs: {
    connection: input({ label: "Connection", type: "connection", required: true }),
  },
  scheduleSupport: "invalid",
  synchronousResponseSupport: "valid",
  perform: async (context, payload, params) => {
    return { payload };
  },
  onInstanceDeploy: async (context, params) => {
    const apiKey = params.connection.fields.apiKey as string;
    const webhookUrl = context.webhookUrls[context.flow.name];

    await fetch("https://api.example.com/webhooks", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ url: webhookUrl, events: ["contact.created"] }),
    });

    context.logger.info("Webhook registered", { webhookUrl });
  },
  onInstanceDelete: async (context, params) => {
    const apiKey = params.connection.fields.apiKey as string;
    const webhookId = context.instanceState.webhookId as string;

    await fetch(`https://api.example.com/webhooks/${webhookId}`, {
      method: "DELETE",
      headers: { Authorization: `Bearer ${apiKey}` },
    });

    context.logger.info("Webhook deregistered", { webhookId });
  },
});

Webhook lifecycle handlers

For more granular webhook management, use webhookLifecycleHandlers instead of onInstanceDeploy/onInstanceDelete. This allows different behavior during create and delete:
webhookLifecycleHandlers: {
  create: async (context, params) => {
    // Register the webhook URL with the third-party service
  },
  delete: async (context, params) => {
    // Deregister the webhook from the third-party service
  },
},

Polling triggers

A polling trigger periodically calls an action to check for new events or records. Use pollingTrigger() instead of trigger() for this pattern.

PollingTriggerDefinition type

interface PollingTriggerDefinition<
  TInputs,
  TConfigVars,
  TPayload,
  TAllowsBranching,
  TResult,
  TActionInputs,
> {
  triggerType?: "polling"; // Set automatically by pollingTrigger()
  display: ActionDisplayDefinition;
  /** The action to invoke on each poll cycle. */
  pollAction?: ActionDefinition<TActionInputs>;
  /** Custom perform function; a default is provided for most polling triggers. */
  perform: PollingTriggerPerformFunction<...>;
  onInstanceDeploy?: TriggerEventFunction<TInputs, TConfigVars>;
  onInstanceDelete?: TriggerEventFunction<TInputs, TConfigVars>;
  inputs?: TInputs;
  allowsBranching?: TAllowsBranching;
  examplePayload?: Awaited<ReturnType<this["perform"]>>;
}
The perform function for polling triggers receives a PollingContext that extends ActionContext with a polling object:
interface PollingContext extends ActionContext {
  polling: {
    /** Invoke the poll action with the given parameters. */
    invokeAction: (params: ActionInputParameters<TInputs>) => Promise<ActionPerformReturn>;
    /** Get the current polling state (persisted between poll cycles). */
    getState: () => Record<string, unknown>;
    /** Persist new polling state for the next cycle. */
    setState: (newState: Record<string, unknown>) => void;
  };
}

Polling trigger example

import { pollingTrigger, action, input } from "@prismatic-io/spectral";

const fetchNewOrders = action({
  display: {
    label: "Fetch New Orders",
    description: "Get orders created since the last poll",
  },
  inputs: {
    connection: input({ label: "Connection", type: "connection", required: true }),
    sinceTimestamp: input({
      label: "Since Timestamp",
      type: "timestamp",
      comments: "Only return orders created after this time",
    }),
  },
  perform: async (context, params) => {
    const apiKey = params.connection.fields.apiKey as string;
    const since = params.sinceTimestamp;

    const response = await fetch(
      `https://api.example.com/orders?created_after=${since}`,
      { headers: { Authorization: `Bearer ${apiKey}` } }
    );
    const orders = await response.json();
    return { data: orders };
  },
});

export const newOrderTrigger = pollingTrigger({
  display: {
    label: "New Order",
    description: "Fires when new orders are found since the last poll",
  },
  pollAction: fetchNewOrders,
  inputs: {
    connection: input({ label: "Connection", type: "connection", required: true }),
  },
  perform: async (context, payload, params) => {
    const state = context.polling.getState();
    const lastPollTime = (state.lastPollTime as string) ?? new Date(0).toISOString();

    const result = await context.polling.invokeAction({
      connection: params.connection,
      sinceTimestamp: lastPollTime,
    });

    const orders = (result?.data as unknown[]) ?? [];
    const hasNewData = orders.length > 0;

    context.polling.setState({ lastPollTime: new Date().toISOString() });

    return {
      payload,
      polledNoChanges: !hasNewData,
    };
  },
});
pollingTrigger() automatically sets triggerType: "polling" on the returned object. You do not need to set it manually.