Multiple triggers in one workflow
Combine incoming issue events with weekday reports using stable trigger keys.
One workflow, a webhook and five schedules
Records issue events as they arrive, and posts yesterday's totals every weekday morning.
import {
defineStep,
defineWorkflow,
NonRetryableError,
webhook,
weekly,
type WorkflowContext,
type WorkflowManifest,
} from '@wix/whenever-workflow-sdk';
export const manifest: WorkflowManifest = {
name: 'Weekday issue report',
triggers: [
webhook({ key: 'issue-events' }),
weekly({ key: 'report-monday', day: 'monday', at: '08:30', tz: 'Europe/Vilnius' }),
weekly({ key: 'report-tuesday', day: 'tuesday', at: '08:30', tz: 'Europe/Vilnius' }),
weekly({ key: 'report-wednesday', day: 'wednesday', at: '08:30', tz: 'Europe/Vilnius' }),
weekly({ key: 'report-thursday', day: 'thursday', at: '08:30', tz: 'Europe/Vilnius' }),
weekly({ key: 'report-friday', day: 'friday', at: '08:30', tz: 'Europe/Vilnius' }),
],
};
type Input = Record<string, unknown> | void;
type Report = { day: string; opened: number; closed: number };
const vilnius = new Intl.DateTimeFormat('en-CA', { timeZone: 'Europe/Vilnius' });
const localDay = (value: number | string) => vilnius.format(new Date(value));
// Monday reports on Friday; every other weekday reports on the day before.
function reportDay(now: number, monday: boolean): string {
const day = new Date(`${localDay(now)}T00:00:00Z`);
day.setUTCDate(day.getUTCDate() - (monday ? 3 : 1));
return day.toISOString().slice(0, 10);
}
export const recordEvent = defineStep(
'Record issue event',
async (ctx: WorkflowContext<Input>, day: string, action: 'opened' | 'closed', number: number) => {
return await ctx.integrations.googlesheets.spreadsheetsValuesAppend({
spreadsheetId: ctx.config.REPORT_SPREADSHEET_ID,
range: 'Events!A:D',
valueInputOption: 'RAW',
values: [[day, action, number, ctx.trigger.deliveryId ?? '']],
});
},
);
export const countEvents = defineStep(
'Count yesterday’s events',
async (ctx: WorkflowContext<Input>) => {
const day = reportDay(ctx.now(), ctx.trigger.key === 'report-monday');
const read = await ctx.integrations.googlesheets.valuesGet({
spreadsheet_id: ctx.config.REPORT_SPREADSHEET_ID,
range: 'Events!A:B',
});
let opened = 0;
let closed = 0;
for (const row of read.values ?? []) {
if (row[0] !== day) continue;
if (row[1] === 'opened') opened++;
if (row[1] === 'closed') closed++;
}
return { day, opened, closed };
},
);
export const postReport = defineStep(
'Post report to Slack',
async (ctx: WorkflowContext<Input>, report: Report) => {
const sent = await ctx.integrations.http.post({
url: 'https://slack.com/api/chat.postMessage',
headers: { Authorization: `Bearer ${ctx.secrets.SLACK_BOT_TOKEN}` },
body: {
channel: ctx.config.SLACK_CHANNEL_ID,
text: `Issues on ${report.day}: ${report.opened} opened, ${report.closed} closed.`,
},
});
if (
typeof sent.body !== 'object' ||
sent.body === null ||
!('ok' in sent.body) ||
sent.body.ok !== true
) {
throw new NonRetryableError('Slack did not accept the report');
}
return sent;
},
);
export default defineWorkflow<
Input,
| { event: Awaited<ReturnType<typeof recordEvent>> }
| { report: Report; slack: Awaited<ReturnType<typeof postReport>> }
| { ignored: string }
>(async (ctx) => {
if (ctx.trigger.type === 'webhook') {
const input = ctx.input;
if (typeof input !== 'object' || input === null) return { ignored: 'empty delivery' };
const issue = input.issue;
if (
typeof issue !== 'object' ||
issue === null ||
!('number' in issue) ||
typeof issue.number !== 'number'
) {
throw new NonRetryableError('The issues event has no issue number');
}
if (
input.action === 'opened' &&
'created_at' in issue &&
typeof issue.created_at === 'string'
) {
return { event: await recordEvent(ctx, localDay(issue.created_at), 'opened', issue.number) };
}
if (input.action === 'closed' && 'closed_at' in issue && typeof issue.closed_at === 'string') {
return { event: await recordEvent(ctx, localDay(issue.closed_at), 'closed', issue.number) };
}
return { ignored: 'neither opened nor closed' };
}
const report = await countEvents(ctx);
const slack = await postReport(ctx, report);
return { report, slack };
});Every trigger carries its own key, and the run reads the one that fired from ctx.trigger: type tells the webhook path from the scheduled one, and key tells Monday, which reports on Friday, from the other weekdays. The webhook's deliveryId goes into the sheet with each event, so a delivery recorded twice can be told apart from two events.
Before you publish
Read what every example assumes, the trigger reference and runtime limits and responsibilities. Validate each trigger path separately and keep the keys stable across edits.