Skip to content

Commit e774461

Browse files
ericallamTrigger.dev RepoOps
authored andcommitted
docs(webhooks): publish the webhooks private beta docs
Mono-RevId: bb6728e6f219c8de7fd58500bb0629e311aecea4
1 parent c9f4581 commit e774461

36 files changed

Lines changed: 3058 additions & 9 deletions

‎.claude/rules/docs-writing.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ paths:
1414
- Link to related pages using relative paths (e.g., `[Tasks](/tasks/overview)`).
1515
- Features behind a flag that isn't fully on:
1616
- Dark (nobody outside the team has it): don't write docs yet.
17-
- Private beta or rolling out: put the docs in the feature PR with `<PrivateBeta flag="<key>" />` or `<RollingOut flag="<key>" />` (import from `/snippets/feature-status.jsx`), at the top of the page or the section. Use `flag="env:<NAME>"` for env-var gates. Keep it on one line with string values.
17+
- Private beta or rolling out: put the docs in the feature PR with `<PrivateBeta flag="<key>" />` or `<RollingOut flag="<key>" />` (import from `/snippets/feature-status.jsx`), at the top of the page or the section. Use `flag="env:<NAME>"` for env-var gates. Keep the opening tag on one line with string values. To add page-specific text (how to get access, say), put it inside the tag: `<PrivateBeta flag="<key>">...</PrivateBeta>`; it renders after the fixed line.
1818
- The PR that removes the flag also removes the callout. CI fails while a callout points at a missing flag.
1919
- Avoid two callouts in a row. A `<PrivateBeta>` or `<RollingOut>` callout next to another callout is the one exception, because it's temporary. Leave the existing callout as it is.
2020
- Only for flags that cloud rolls out. Never add it to self-host config docs.

‎docs/ai-chat/actions.mdx‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,16 @@ onAction: async ({ action }) => {
7373
chat.history.slice(0, -1);
7474
chat.inject([{ role: "system", content: "Answer formally this time." }]);
7575
return chat.turn(); // with a one-shot instruction
76+
77+
case "ask":
78+
return chat.turn(action.question); // answer a new user message
7679
}
7780
}
7881
```
7982

80-
`run()` receives the edited history with no incoming user message, the same shape as a `regenerate-message` turn, and its `trigger` is `"action-turn"`, so a `run()` that returns early on `"action"` (the pre-May behaviour, when actions invoked `run()` directly) still answers. Returning anything other than `chat.turn()` or nothing is an error; a response can no longer be returned from `onAction` directly.
83+
`chat.turn(message)` appends `message` (a string, or a `UIMessage`) to the history as a user message after the edit, and the turn answers it. That is how a button click becomes a question, such as a follow-up suggestion in [Slack](/webhooks/slack#buttons-that-send-the-agent-an-action).
84+
85+
Without a message, `run()` receives the edited history with no incoming user message, the same shape as a `regenerate-message` turn, and its `trigger` is `"action-turn"`, so a `run()` that returns early on `"action"` (the pre-May behaviour, when actions invoked `run()` directly) still answers. Returning anything other than `chat.turn()` or nothing is an error; a response can no longer be returned from `onAction` directly.
8186

8287
### Actions and persistence
8388

‎docs/ai-chat/backend.mdx‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ sidebarTitle: "Backend"
44
description: "Three approaches to building your chat backend — chat.agent(), session iterator, or raw task primitives."
55
---
66

7+
import WebhooksBetaNote from "/snippets/webhooks-beta-note.mdx";
8+
79
There are three abstraction levels for a chat backend. All three speak the same wire protocol, so the [frontend transport](/ai-chat/frontend) works unchanged whichever you pick.
810

911
| Capability | `chat.agent()` | `chat.createSession()` | Raw primitives |
@@ -497,6 +499,44 @@ Custom actions let the frontend send structured commands (undo, rollback, edit,
497499

498500
See [Actions](/ai-chat/actions).
499501

502+
### Webhook events and channels
503+
504+
<WebhooksBetaNote />
505+
506+
Two `chat.agent()` options wire an agent to verified inbound webhooks. `events` claims [`chat.event(...)`](/webhooks/session-routing) descriptors: each verified delivery is routed to this agent's session and arrives at `onAction` as an action (not a turn), so [session routing](/webhooks/session-routing) decides which conversation it lands on. `channels` claims channel connectors that turn an external chat surface into a frontend for the agent: an inbound message runs as a turn through `run()` and the reply is posted back. `slack()` ships in `@trigger.dev/slack`, and `chat.channels.custom(...)` builds a connector for any endpoint without a preset.
507+
508+
```ts
509+
import { webhooks } from "@trigger.dev/sdk";
510+
import { chat } from "@trigger.dev/sdk/ai";
511+
import { slack, webhookSource } from "@trigger.dev/slack";
512+
513+
const slackEvents = webhooks.endpoint.define({ id: "slack", source: webhookSource() });
514+
const stripe = webhooks.endpoint.define({ id: "stripe", source: webhooks.stripe() });
515+
516+
export const orderEvents = chat.event({
517+
id: "order-events",
518+
endpoint: stripe,
519+
key: "{event.data.object.customer}",
520+
// invoices always carry a customer; events without one would have no session to go to
521+
filter: "event.type startsWith 'invoice.'",
522+
type: "order.event",
523+
});
524+
525+
export const myChat = chat.agent({
526+
id: "my-chat",
527+
events: [orderEvents],
528+
channels: [slack({ id: "support-slack", endpoint: slackEvents, token: process.env.SLACK_BOT_TOKEN! })],
529+
onAction: async ({ action }) => {
530+
// A verified order-events delivery arrives here as an action.
531+
},
532+
run: async (payload) => {
533+
// Inbound Slack messages run here as normal turns.
534+
},
535+
});
536+
```
537+
538+
See [session routing](/webhooks/session-routing) and [channels](/webhooks/channels). For the interactive approvals layer, where a turn pauses on a human decision (buttons in the thread) and resumes on the click, see [human-in-the-loop](/webhooks/human-in-the-loop).
539+
500540
### Chat history
501541

502542
Imperative API for reading and modifying the accumulated message history. Works from any hook (`onAction`, `onTurnStart`, `onBeforeTurnComplete`, `onTurnComplete`, `hydrateMessages`) or from `run()` and AI SDK tools.

‎docs/ai-chat/pending-messages.mdx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ By default (without `pendingMessages`), a message sent while the agent is respon
1212

1313
The `pendingMessages` option enables steering instead, injecting user messages between tool-call steps via the AI SDK's `prepareStep`. Messages that arrive during streaming are queued and injected at the next step boundary. A message that is not injected becomes the next turn instead, whether that is because `shouldInject` returned `false` or because there were no more step boundaries (single-step response or final text generation). The backend handles that, so no client-side re-send is involved.
1414

15+
Messages from a [channel](/webhooks/channels) such as [Slack](/webhooks/slack) are offered the same way, with `metadata.source` set to `"channel"` so `shouldInject` can tell them apart. A channel's stop control and approval clicks are never offered. The connector acknowledges a channel message as soon as it arrives (Slack reacts with 👀, Linear posts a thought), before `shouldInject` decides, so the acknowledgement is the same whether the message is folded in or answered next. Channels are part of the [webhooks private beta](https://trigger.dev/webhooks-beta).
16+
1517
Use the `streamText` passed to your agent's `run` callback. It wires up pending-message injection automatically. If you import `streamText` directly from `ai`, spread `chat.toStreamTextOptions()` into its options to connect injection.
1618

1719
## How it works

‎docs/ai-chat/reference.mdx‎

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ Options for `chat.agent()`.
4747
| `storage` | `TranscriptStorage` | `defaultStorage` | Where the conversation is persisted and read back. The platform snapshot by default; bring your own to write each change to your database. See [Transcript storage](/ai-chat/transcript-storage) |
4848
| `hydrateMessages` | `(event: HydrateMessagesEvent) => UIMessage[] \| Promise<UIMessage[]>` | — | **Deprecated.** Load message history from backend, replacing the linear accumulator. Use `loadContext` on a `storage` instead; cannot be combined with `storage`. See [hydrateMessages](/ai-chat/lifecycle-hooks#hydratemessages) |
4949
| `actionSchema` | `TaskSchema` | — | Schema for validating custom actions sent via `transport.sendAction()`. See [Actions](/ai-chat/actions) |
50-
| `onAction` | `(event: ActionEvent) => Promise<void \| ActionTurn> \| void \| ActionTurn` | — | Handle custom actions. Actions are state edits: only `hydrateMessages` (or a storage's `loadContext`) + `onAction` fire. Return `chat.turn()` to run a turn on the edited history, or nothing for an edit only. See [Actions](/ai-chat/actions) |
50+
| `onAction` | `(event: ActionEvent) => Promise<void \| ActionTurn> \| void \| ActionTurn` | — | Handle custom actions. Actions are state edits: only `hydrateMessages` (or a storage's `loadContext`) + `onAction` fire. Return `chat.turn()` to run a turn on the edited history (`chat.turn(message)` to answer a new user message), or nothing for an edit only. See [Actions](/ai-chat/actions) |
51+
| `events` | `ChatEvent[]` | — | Webhook event descriptors (from `chat.event()`) whose verified deliveries are routed to this agent as actions and handled in `onAction`. See [session routing](/webhooks/session-routing). Part of the [webhooks private beta](https://trigger.dev/webhooks-beta). |
52+
| `channels` | `ChannelConnector[]` | — | Channel connectors (for example `slack()`) that turn an external chat surface into a frontend for the agent: inbound messages run as turns and the reply posts back. See [channels](/webhooks/channels). Part of the [webhooks private beta](https://trigger.dev/webhooks-beta). |
5153
| `onTurnStart` | `(event: TurnStartEvent) => Promise<void> \| void` | — | Fires every turn before `run()` |
5254
| `onBeforeTurnComplete` | `(event: BeforeTurnCompleteEvent) => Promise<void> \| void` | — | Fires after response but before stream closes. Includes `writer`. |
5355
| `onTurnComplete` | `(event: TurnCompleteEvent) => Promise<void> \| void` | — | Fires after each turn completes (stream closed) |
@@ -232,7 +234,7 @@ The persistence adapter set through `chat.agent({ storage })`. See [Transcript s
232234
| Type | Shape |
233235
| --- | --- |
234236
| `TranscriptScope<TClientData>` | `{ chatId: string; clientData: TClientData }` |
235-
| `TranscriptStorageContext<TClientData>` | `TranscriptScope` plus `{ turn: number; trigger: "submit-message" \| "regenerate-message" \| "action"; runId: string; ctx: TaskRunContext }` |
237+
| `TranscriptStorageContext<TClientData>` | `TranscriptScope` plus `{ turn: number; trigger: "submit-message" \| "regenerate-message" \| "action" \| "action-turn"; source?: "client" \| "channel" \| "webhook"; connectorId?: string; runId: string; ctx: TaskRunContext }`. `action-turn` is the turn `chat.turn()` requested; `source` names the surface that drove the write |
236238
| `TranscriptChange` | `{ op: "put"; message: UIMessage; final?: boolean }` \| `{ op: "remove"; id: string }` \| `{ op: "truncateAfter"; afterId: string }` \| `{ op: "state"; value: unknown \| null }` |
237239
| `TranscriptChangeset` | `{ reason: "turn-complete" \| "turn-error" \| "action" \| "compaction" \| "recovery"; changes: TranscriptChange[]; transcript: TranscriptState; cursors?: TranscriptCursors }` |
238240
| `TranscriptState` | `{ entries: Array<{ id: string; final: boolean; message: UIMessage }>; state: unknown \| null }`: the whole conversation after the changeset's changes, for stores that write one document |
@@ -275,6 +277,7 @@ Passed to the `onAction` callback. See [Actions](/ai-chat/actions).
275277
| `clientData` | Typed by `clientDataSchema` | Custom data from the frontend |
276278
| `uiMessages` | `UIMessage[]` | Accumulated UI messages (after hydration, if set) |
277279
| `messages` | `ModelMessage[]` | Accumulated model messages (after hydration, if set) |
280+
| `channel` | `{ connectorId, provider, event, deliveryId }` | For a channel action (a button the agent posted, such as a Slack `slackButton()`): the verified click event. Absent otherwise |
278281

279282
## TurnStartEvent
280283

@@ -538,6 +541,8 @@ All methods available on the `chat` object from `@trigger.dev/sdk/ai`.
538541
| Method | Description |
539542
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
540543
| `chat.agent(options)` | Create a chat agent |
544+
| `chat.event(options)` | Declare an inbound webhook event descriptor an agent claims via `chat.agent({ events })`. See [session routing](/webhooks/session-routing). Part of the [webhooks private beta](https://trigger.dev/webhooks-beta). |
545+
| `chat.channels.custom(options)` | Create a generic chat-frontend channel over any verified webhook source (you supply the egress). The `slack()` preset ships in `@trigger.dev/slack`. See [channels](/webhooks/channels). Part of the [webhooks private beta](https://trigger.dev/webhooks-beta). |
541546
| `chat.createSession(payload, options)` | Create an async iterator for chat turns |
542547
| `chat.pipe(source, options?)` | Pipe a stream to the frontend (from anywhere inside a task) |
543548
| `chat.pipeAndCapture(source, options?)` | Pipe and capture the response; returns `{ message, status, error }` |
@@ -570,6 +575,51 @@ All methods available on the `chat` object from `@trigger.dev/sdk/ai`.
570575
| `chat.withUIMessage(config?)` | Returns a [ChatBuilder](/ai-chat/types#chatbuilder) with a fixed `UIMessage` subtype. See [Types](/ai-chat/types) |
571576
| `chat.withClientData({ schema })` | Returns a [ChatBuilder](/ai-chat/types#chatbuilder) with a fixed client data schema. See [Types](/ai-chat/types#typed-client-data-with-chatwithclientdata) |
572577

578+
## `chat.event`
579+
580+
Declare an inbound webhook event that an agent claims via [`events`](#chatagentoptions) on `chat.agent()`. It is a descriptor only, with no handler: it names the [endpoint](/webhooks/overview) it subscribes to, a `key` template that resolves each delivery to a durable [session](/ai-chat/sessions), and an optional `type` label (defaults to the descriptor `id`). Verified deliveries are routed to that session and arrive at `onAction` as a `{ type, event, source, headers, deliveryId, endpoint }` envelope, not as a chat turn. See [session routing](/webhooks/session-routing).
581+
582+
```ts
583+
import { webhooks } from "@trigger.dev/sdk";
584+
import { chat } from "@trigger.dev/sdk/ai";
585+
586+
export const stripe = webhooks.endpoint.define({ id: "stripe", source: webhooks.stripe() });
587+
588+
export const orderEvents = chat.event({
589+
id: "order-events",
590+
endpoint: stripe,
591+
key: "{event.data.object.customer}",
592+
// invoices always carry a customer; events without one would have no session to go to
593+
filter: "event.type startsWith 'invoice.'",
594+
type: "order.event",
595+
});
596+
```
597+
598+
## `chat.channels.custom`
599+
600+
Create a generic chat-frontend channel on any [endpoint](/webhooks/overview), claimed via [`channels`](#chatagentoptions) on `chat.agent()`. You supply the session `key`, the `inbound` map from event to turn message, and your own `send` egress that posts the reply back, so the whole round-trip is under your control. Inbound messages run as normal turns and the reply is posted back. The `slack()` preset ships in `@trigger.dev/slack` and wires the egress for you. See [channels](/webhooks/channels), and the interactive approvals layer at [human-in-the-loop](/webhooks/human-in-the-loop).
601+
602+
```ts
603+
import { webhooks } from "@trigger.dev/sdk";
604+
import { chat } from "@trigger.dev/sdk/ai";
605+
606+
const surface = webhooks.endpoint.define({
607+
id: "my-surface",
608+
source: webhooks.custom<MyEvent>({ /* verifier config */ }),
609+
});
610+
611+
export const mySurface = chat.channels.custom({
612+
id: "my-surface",
613+
endpoint: surface,
614+
key: "{event.conversationId}",
615+
inbound: (event) => event.text,
616+
send: async (message, ctx) => {
617+
const ref = await postToMySurface(ctx.event, message.text, ctx.previousRef);
618+
return { ref };
619+
},
620+
});
621+
```
622+
573623
## `chat.withUIMessage`
574624

575625
Returns a [`ChatBuilder`](/ai-chat/types#chatbuilder) with a fixed `UIMessage` subtype. Chain `.withClientData()`, hook methods, and `.agent()`.

‎docs/docs.json‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,22 @@
159159
}
160160
]
161161
},
162+
{
163+
"group": "Webhooks",
164+
"pages": [
165+
"webhooks/overview",
166+
"webhooks/sources",
167+
"webhooks/connect",
168+
"webhooks/deliveries",
169+
"webhooks/filters",
170+
"webhooks/waiters",
171+
"webhooks/session-routing",
172+
"webhooks/channels",
173+
"webhooks/slack",
174+
"webhooks/linear",
175+
"webhooks/human-in-the-loop"
176+
]
177+
},
162178
{
163179
"group": "Configuration",
164180
"pages": [
@@ -386,6 +402,22 @@
386402
"management/errors/unresolve"
387403
]
388404
},
405+
{
406+
"group": "Webhooks API",
407+
"pages": [
408+
"management/webhooks/list-endpoints",
409+
"management/webhooks/retrieve-endpoint",
410+
"management/webhooks/enable-endpoint",
411+
"management/webhooks/disable-endpoint",
412+
"management/webhooks/generate-secret",
413+
"management/webhooks/set-secret",
414+
"management/webhooks/list-deliveries",
415+
"management/webhooks/retrieve-delivery",
416+
"management/webhooks/replay-delivery",
417+
"management/webhooks/create-waiter",
418+
"management/webhooks/cancel-waiter"
419+
]
420+
},
389421
{
390422
"group": "Queues API",
391423
"pages": [
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
title: "Cancel a webhook waiter"
3+
openapi: "v3-openapi POST /api/v1/webhooks/waiters/{waiterId}/cancel"
4+
---
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
title: "Create a webhook waiter"
3+
openapi: "v3-openapi POST /api/v1/webhooks/endpoints/{endpointId}/waiters"
4+
---
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
title: "Disable a webhook endpoint"
3+
openapi: "v3-openapi POST /api/v1/webhooks/endpoints/{endpointId}/disable"
4+
---
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
title: "Enable a webhook endpoint"
3+
openapi: "v3-openapi POST /api/v1/webhooks/endpoints/{endpointId}/enable"
4+
---

0 commit comments

Comments
 (0)