Skip to main content
A common goal is “run this when something happens in another app,” such as a new GitHub issue, a paid Stripe invoice, or a Linear status change. Keystroke doesn’t have a separate app-event trigger type; instead you react to app events with the trigger sources you already have, pointed at the app. This page covers the patterns.
There’s no defineAppEventSource. The three sources (webhook, schedule, and poll) cover app events. Pick the one that matches how the app delivers events.

When the app sends webhooks

Most modern apps can push events to a URL. This is the best option when it’s available: point the app’s webhook at a webhook trigger.
  1. Define a defineWebhookSource with a request schema for the app’s payload.
  2. Get the URL with keystroke triggers url <endpoint> and register it in the app’s settings.
  3. Use a filter to narrow to the event types you care about.
src/triggers/stripe-invoice-paid.ts
import { defineWebhookSource } from "@keystrokehq/keystroke/trigger";
import { z } from "zod";
import workflow from "../workflows/invoice-paid";

export default defineWebhookSource({
  slug: "stripe-invoice-paid",
  endpoint: "stripe",
  request: z.object({ type: z.string(), data: z.object({ id: z.string() }) }),
  filter: z.object({ type: z.literal("invoice.paid") }),
}).attach({
  workflow,
  transform: (payload) => ({ invoiceId: (payload as { data: { id: string } }).data.id }),
});
Providers that send every event type to one URL fit the shared endpoint pattern: many triggers on a single endpoint, each filtering for its own event.

When the app doesn’t push events

If an app has no webhooks (or you’d rather pull), use a poll trigger. Its run() checks the app’s API on a schedule and starts a run only when there’s something new:
src/triggers/new-issues.ts
import { definePollSource } from "@keystrokehq/keystroke/trigger";
import workflow from "../workflows/handle-issue";

export default definePollSource({
  slug: "new-issues",
  schedule: "*/10 * * * *",
  run: async () => fetchOpenIssues(),
  filter: (result) => result.issues.length > 0,
}).attach({ workflow });
When the source you’re polling needs credentials, resolve them through an action that you call from run(), or read them from your project’s credentials.

Chat apps: the Slack gateway

Conversations are different from events. To let people talk to an agent from Slack (mention it in a channel or DM it), use the Slack gateway rather than a trigger. The gateway delivers inbound messages to an agent and posts replies back. See external channels.

Finding integrations

Keystroke ships 1,000+ integrations. Each one lists the actions it provides (and whether it exposes triggers), which you can use inside a poll’s run() or as steps and tools once connected. Browse the catalog to see what’s available for the app you want to react to, then wire it up with a webhook or poll as above.

Next steps

Webhooks

Receive events the app pushes to a URL.

Polling

Pull events from an app on a schedule.

External channels

Connect agents to Slack and other chat surfaces.

Integrations

Browse 1,000+ built-in integrations.