@trigger.dev/sdk 4.8.0-webhooks.20261020092920 → 4.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commonjs/v3/ai.d.ts +2 -1
- package/dist/commonjs/v3/ai.js +18 -1
- package/dist/commonjs/v3/ai.js.map +1 -1
- package/dist/commonjs/v3/webhooks.d.ts +11 -3
- package/dist/commonjs/v3/webhooks.js +17 -1
- package/dist/commonjs/v3/webhooks.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/v3/ai.d.ts +2 -1
- package/dist/esm/v3/ai.js +18 -1
- package/dist/esm/v3/ai.js.map +1 -1
- package/dist/esm/v3/webhooks.d.ts +11 -3
- package/dist/esm/v3/webhooks.js +17 -1
- package/dist/esm/v3/webhooks.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/docs/ai/prompts.mdx +9 -6
- package/docs/ai-chat/backend.mdx +10 -7
- package/docs/ai-chat/chat-local.mdx +4 -4
- package/docs/ai-chat/client-protocol.mdx +6 -3
- package/docs/ai-chat/fast-starts.mdx +1 -1
- package/docs/ai-chat/overview.mdx +4 -3
- package/docs/ai-chat/patterns/database-persistence.mdx +3 -3
- package/docs/ai-chat/patterns/native-compaction.mdx +6 -6
- package/docs/ai-chat/patterns/redacting-telemetry.mdx +5 -5
- package/docs/ai-chat/patterns/sub-agents.mdx +1 -1
- package/docs/ai-chat/patterns/version-upgrades.mdx +3 -3
- package/docs/ai-chat/pending-messages.mdx +1 -1
- package/docs/ai-chat/reference.mdx +4 -4
- package/docs/ai-chat/side-channels.mdx +2 -2
- package/docs/ai-chat/types.mdx +5 -5
- package/docs/apikeys.mdx +1 -1
- package/docs/database-connections.mdx +2 -2
- package/docs/deploy-environment-variables.mdx +19 -0
- package/docs/introduction.mdx +104 -24
- package/docs/mcp-tools.mdx +5 -4
- package/docs/self-hosting/env/supervisor.mdx +1 -0
- package/docs/self-hosting/env/webapp.mdx +24 -0
- package/docs/self-hosting/overview.mdx +2 -0
- package/docs/webhooks/channels.mdx +29 -7
- package/docs/webhooks/connect.mdx +14 -7
- package/docs/webhooks/deliveries.mdx +8 -5
- package/docs/webhooks/filters.mdx +8 -4
- package/docs/webhooks/human-in-the-loop.mdx +9 -6
- package/docs/webhooks/linear.mdx +2 -5
- package/docs/webhooks/overview.mdx +16 -4
- package/docs/webhooks/session-routing.mdx +8 -5
- package/docs/webhooks/slack.mdx +44 -19
- package/docs/webhooks/sources.mdx +36 -7
- package/docs/webhooks/waiters.mdx +0 -4
- package/package.json +2 -2
|
@@ -4,10 +4,6 @@ description: "Receive provider webhooks on hosted, verified endpoints and route
|
|
|
4
4
|
sidebarTitle: "Overview"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
import WebhooksBetaBanner from "/snippets/webhooks-beta-banner.mdx";
|
|
8
|
-
|
|
9
|
-
<WebhooksBetaBanner />
|
|
10
|
-
|
|
11
7
|
**A webhook endpoint is one hosted URL that you register with a provider.** Trigger.dev verifies every request against the endpoint's [source](/webhooks/sources), records it as a [delivery](/webhooks/deliveries), and hands the event to everything subscribed to the endpoint.
|
|
12
8
|
|
|
13
9
|
Declare the endpoint once, then subscribe to it:
|
|
@@ -63,6 +59,22 @@ Each subscriber has its own [filter](/webhooks/filters), so one delivery can rea
|
|
|
63
59
|
| `endpoint` | The endpoint the delivery arrived on: `id`, `declaredId`, `tenantId`, `externalRef`, and the `metadata` you declared. |
|
|
64
60
|
| `ctx` | The same [run context](/context) that regular tasks receive. |
|
|
65
61
|
|
|
62
|
+
`webhook()` also takes the options a [task](/tasks/overview) does, such as `queue`, `concurrency`, `machine`, `retry`, `maxDuration` and the lifecycle hooks. For example, to handle one delivery at a time:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
export const orders = webhook({
|
|
66
|
+
id: "stripe-orders",
|
|
67
|
+
endpoint: stripe,
|
|
68
|
+
queue: { concurrencyLimit: 1 },
|
|
69
|
+
retry: { maxAttempts: 5 },
|
|
70
|
+
onEvent: async ({ event }) => {
|
|
71
|
+
// ...
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The limit covers every delivery the task handles. To process each customer's events in order while different customers run in parallel, route them to an agent with [session routing](/webhooks/session-routing): deliveries with the same key land in one session and are handled one after another.
|
|
77
|
+
|
|
66
78
|
`webhooks.endpoint.define` also takes a `description` and `metadata`. The metadata is passed to every subscriber as `endpoint.metadata`. The endpoint `id` can't start with `wh_`, which is reserved for the ids Trigger.dev generates.
|
|
67
79
|
|
|
68
80
|
## How it works
|
|
@@ -4,10 +4,6 @@ description: "Route verified webhook deliveries to a durable per-key session ins
|
|
|
4
4
|
sidebarTitle: "Session routing"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
import WebhooksBetaBanner from "/snippets/webhooks-beta-banner.mdx";
|
|
8
|
-
|
|
9
|
-
<WebhooksBetaBanner />
|
|
10
|
-
|
|
11
7
|
A plain [`webhook()`](/webhooks/overview) runs a fresh, stateless run for every delivery. Sometimes you want the opposite: deliveries that share a key (a customer, an installation, an issue) should land on **one durable [session](/ai-chat/sessions)** and be handled in order, with state carried across them. That is what session routing does.
|
|
12
8
|
|
|
13
9
|
Instead of a handler, you declare a `chat.event` on an [endpoint](/webhooks/overview) and list it on an agent. The verified delivery is routed to a find-or-created session and arrives as an [action](/ai-chat/actions).
|
|
@@ -87,7 +83,14 @@ When the same session is also a [channel](/webhooks/channels) conversation (a Sl
|
|
|
87
83
|
```ts
|
|
88
84
|
export const orderAgent = chat.agent({
|
|
89
85
|
id: "order-agent",
|
|
90
|
-
channels: [
|
|
86
|
+
channels: [
|
|
87
|
+
slack({
|
|
88
|
+
id: "support-slack",
|
|
89
|
+
endpoint: slackEvents,
|
|
90
|
+
token: process.env.SLACK_BOT_TOKEN!,
|
|
91
|
+
startOn: appMentions(),
|
|
92
|
+
}),
|
|
93
|
+
],
|
|
91
94
|
events: [orderEvents],
|
|
92
95
|
onAction: async ({ action }) => {
|
|
93
96
|
if (action.type === "order.event") {
|
package/docs/webhooks/slack.mdx
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Slack agents"
|
|
3
|
-
description: "Turn a chat.agent into a Slack agent: users message it in Slack, it shows its status while it works, streams its reply with tool calls as
|
|
3
|
+
description: "Turn a chat.agent into a Slack agent: users message it in Slack, it shows its status while it works, streams its reply with its tool calls as a plan, asks for approvals, and stops on Slack's stop button."
|
|
4
4
|
sidebarTitle: "Slack agents"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
import WebhooksBetaBanner from "/snippets/webhooks-beta-banner.mdx";
|
|
8
|
-
|
|
9
|
-
<WebhooksBetaBanner />
|
|
10
|
-
|
|
11
7
|
**`slack()` from `@trigger.dev/slack` turns a `chat.agent` into a native Slack agent.** Each Slack agent session (a conversation with the agent, or a thread in a channel) becomes a durable Trigger.dev [session](/ai-chat/sessions). A message runs as a turn, the session shows the agent working, the reply streams into the thread as the model writes it, and Slack's stop button stops the turn.
|
|
12
8
|
|
|
13
9
|
```ts /trigger/support.ts
|
|
14
10
|
import { webhooks } from "@trigger.dev/sdk";
|
|
15
11
|
import { chat } from "@trigger.dev/sdk/ai";
|
|
16
|
-
import { slack, webhookSource } from "@trigger.dev/slack";
|
|
12
|
+
import { appMentions, slack, webhookSource } from "@trigger.dev/slack";
|
|
17
13
|
import { anthropic } from "@ai-sdk/anthropic";
|
|
18
14
|
|
|
19
15
|
const slackEvents = webhooks.endpoint.define({ id: "slack", source: webhookSource() });
|
|
20
16
|
|
|
21
17
|
export const support = chat.agent({
|
|
22
18
|
id: "support",
|
|
23
|
-
channels: [
|
|
19
|
+
channels: [
|
|
20
|
+
slack({
|
|
21
|
+
id: "slack",
|
|
22
|
+
endpoint: slackEvents,
|
|
23
|
+
token: process.env.SLACK_BOT_TOKEN!,
|
|
24
|
+
startOn: appMentions(),
|
|
25
|
+
}),
|
|
26
|
+
],
|
|
24
27
|
tools: { searchDocs, refundOrder },
|
|
25
28
|
run: async ({ messages, tools, signal, streamText }) =>
|
|
26
29
|
streamText({ model: anthropic("claude-sonnet-4-5"), messages, tools, abortSignal: signal }),
|
|
@@ -58,6 +61,7 @@ export const support = chat.agent({
|
|
|
58
61
|
- files:read
|
|
59
62
|
- reactions:write
|
|
60
63
|
- im:history
|
|
64
|
+
- app_mentions:read
|
|
61
65
|
- channels:history
|
|
62
66
|
- groups:history
|
|
63
67
|
settings:
|
|
@@ -65,6 +69,7 @@ export const support = chat.agent({
|
|
|
65
69
|
request_url: https://your-endpoint-url
|
|
66
70
|
bot_events:
|
|
67
71
|
- message.im
|
|
72
|
+
- app_mention
|
|
68
73
|
- message.channels
|
|
69
74
|
- message.groups
|
|
70
75
|
- agent_session_stopped
|
|
@@ -73,7 +78,7 @@ export const support = chat.agent({
|
|
|
73
78
|
request_url: https://your-endpoint-url
|
|
74
79
|
```
|
|
75
80
|
|
|
76
|
-
`agent_view` makes the app an agent, `agent_session_stopped` turns on the stop button, interactivity delivers clicks on the agent's buttons, `files:read` lets the agent read the images and PDFs users share with it,
|
|
81
|
+
`agent_view` makes the app an agent, `agent_session_stopped` turns on the stop button, interactivity delivers clicks on the agent's buttons, `files:read` lets the agent read the images and PDFs users share with it, `reactions:write` lets it mark a message it picked up mid-reply, and `app_mention` with `app_mentions:read` lets an @mention start a session in a channel (see [Channel threads](#channel-threads)). Drop `app_mention`, `message.channels`, `message.groups` and their scopes if the agent should only answer in its own conversations.
|
|
77
82
|
</Step>
|
|
78
83
|
<Step title="Connect the endpoint">
|
|
79
84
|
Paste the app's **Signing Secret** (Basic Information, App Credentials) as the endpoint's signing secret. Until it is set the endpoint rejects Slack's signed requests, so Slack shows the request URL as not verified: once the secret is in, click **Verify** on the app's Event Subscriptions page (or **Click here to verify** on the App Manifest page). Install the app to your workspace and pass the bot token (`xoxb-...`) as `token`. The endpoint's setup prompt walks an AI coding agent through these settings.
|
|
@@ -89,18 +94,20 @@ export const support = chat.agent({
|
|
|
89
94
|
| --- | --- |
|
|
90
95
|
| A turn starts | The session's status is **processing**. On the first turn the session is titled with the prompt. |
|
|
91
96
|
| The model writes | The reply streams into the thread with `chat.startStream`, `chat.appendStream` and `chat.stopStream`, as markdown. |
|
|
92
|
-
| A tool is called | A task
|
|
97
|
+
| A tool is called | A task in the reply's plan, titled by the tool's `title` (or its name made readable), marked complete when the tool returns and failed if it throws. The plan reads "Working on it", then "Done", "Stopped" or "Waiting for approval" when the turn ends. |
|
|
93
98
|
| A tool needs a decision | Approve and Deny buttons, and the status is **suspended** (see [Approvals and buttons](#approvals-and-buttons)). |
|
|
99
|
+
| The turn fails | The reply ends with "Something went wrong answering this. Try again." (`errorMessage` changes it). The error is logged on the run. |
|
|
94
100
|
| The turn ends | The status goes back to **active**, whether it answered, stopped, failed or had nothing to say. |
|
|
95
101
|
| `chat.close()` | The status is **closed**. |
|
|
96
102
|
|
|
97
|
-
Set `title` to name the session yourself; return null to keep Slack's own. A title the user sets in Slack is never overwritten. Slack drops a processing status after an hour, so a longer turn re-sends it every 50 minutes. `progress: "off"` streams the reply without
|
|
103
|
+
Set `title` to name the session yourself; return null to keep Slack's own. A title the user sets in Slack is never overwritten. Slack drops a processing status after an hour, so a longer turn re-sends it every 50 minutes. Give a tool a readable task title with the AI SDK's `tool({ title: "Search the archive", ... })`; without one the task shows the tool's name made readable (`searchArchive` shows "Search archive"). `progress: "tasks"` shows one task card per call between the reply's text instead of a plan, `progress: "off"` streams the reply without them, and `delivery: "final"` posts the whole reply once the turn ends instead of streaming it.
|
|
98
104
|
|
|
99
105
|
```ts
|
|
100
106
|
slack({
|
|
101
107
|
id: "slack",
|
|
102
108
|
endpoint: slackEvents,
|
|
103
109
|
token,
|
|
110
|
+
startOn: appMentions(),
|
|
104
111
|
title: (prompt) => `Support: ${prompt.slice(0, 80)}`,
|
|
105
112
|
});
|
|
106
113
|
```
|
|
@@ -118,6 +125,7 @@ slack({
|
|
|
118
125
|
id: "slack",
|
|
119
126
|
endpoint: slackEvents,
|
|
120
127
|
token,
|
|
128
|
+
startOn: appMentions(),
|
|
121
129
|
approval: (call, { button }) => ({
|
|
122
130
|
text: `Run ${call.toolName}?`,
|
|
123
131
|
blocks: [
|
|
@@ -144,6 +152,7 @@ export const moji = chat.agent({
|
|
|
144
152
|
id: "slack",
|
|
145
153
|
endpoint: slackEvents,
|
|
146
154
|
token,
|
|
155
|
+
startOn: appMentions(),
|
|
147
156
|
interactions: {
|
|
148
157
|
pickEmoji: (call, { button }) => {
|
|
149
158
|
const { options } = call.input as { options: { name: string }[] };
|
|
@@ -181,18 +190,18 @@ The click resolves `pickEmoji` to `{ picked: "wizard" }`, the buttons collapse t
|
|
|
181
190
|
Buttons that aren't answering a tool, such as follow-up questions, are built with `slackButton()` and posted with the surface's `post()`. A click reaches `onAction`, validated against `actionSchema` like a browser action, and returning `chat.turn(message)` answers it in the thread:
|
|
182
191
|
|
|
183
192
|
```ts
|
|
184
|
-
import { slackButton, type
|
|
193
|
+
import { slackButton, type SlackRunSurface } from "@trigger.dev/slack";
|
|
185
194
|
|
|
186
195
|
export const support = chat.agent({
|
|
187
196
|
id: "support",
|
|
188
|
-
channels: [slack({ id: "slack", endpoint: slackEvents, token })],
|
|
197
|
+
channels: [slack({ id: "slack", endpoint: slackEvents, token, startOn: appMentions() })],
|
|
189
198
|
actionSchema: z.object({ type: z.literal("ask"), question: z.string() }),
|
|
190
199
|
tools: {
|
|
191
200
|
offerFollowUps: tool({
|
|
192
201
|
description: "Offer the user up to three follow-up questions as buttons",
|
|
193
202
|
inputSchema: z.object({ questions: z.array(z.string()).max(3) }),
|
|
194
203
|
execute: async ({ questions }) => {
|
|
195
|
-
await chat.surface<
|
|
204
|
+
await chat.surface<SlackRunSurface>()?.post({
|
|
196
205
|
text: "Follow-up questions",
|
|
197
206
|
blocks: [
|
|
198
207
|
{
|
|
@@ -244,14 +253,27 @@ Slack shows a stop button while the agent is working. It aborts the running turn
|
|
|
244
253
|
|
|
245
254
|
## Channel threads
|
|
246
255
|
|
|
247
|
-
|
|
256
|
+
`startOn` is required: it decides which channel messages start a session. Pass `appMentions()` to have the agent answer only when it is @mentioned. A mention in a channel starts a session and the agent answers in a thread under it, then follows the rest of that thread without being mentioned again. `startOn` only applies to channels: a message in the agent's own conversation always starts a session.
|
|
248
257
|
|
|
249
258
|
```ts
|
|
250
|
-
import { slack,
|
|
259
|
+
import { slack, appMentions } from "@trigger.dev/slack";
|
|
251
260
|
|
|
252
|
-
slack({ id: "slack", endpoint: slackEvents, token, startOn:
|
|
261
|
+
slack({ id: "slack", endpoint: slackEvents, token, startOn: appMentions() });
|
|
253
262
|
```
|
|
254
263
|
|
|
264
|
+
`appMentions()` uses Slack's `app_mention` event, so the app needs the `app_mention` bot event and the `app_mentions:read` scope (both in the manifest above), and no bot user id. Slack also delivers each mention as an ordinary channel message; the connector ignores that copy, so one mention runs one turn.
|
|
265
|
+
|
|
266
|
+
What each value does:
|
|
267
|
+
|
|
268
|
+
| `startOn` | The agent starts a session in a channel |
|
|
269
|
+
| --- | --- |
|
|
270
|
+
| `appMentions()` | When it is @mentioned (recommended). |
|
|
271
|
+
| `mentions("U012BOT")` | When a message mentions that bot user id, from message events, without the `app_mention` subscription. |
|
|
272
|
+
| `"event.event.type == 'message'"` | On every message it can see. |
|
|
273
|
+
| `"event.event.channel_type == 'im'"` | Never: it answers only in its own conversation. |
|
|
274
|
+
|
|
275
|
+
In a channel shared with another organization through Slack Connect, pass `ownWorkspaceOnly: true` to answer only people from your own workspace. Messages, mentions and button clicks from the other organization's users are dropped before they reach the agent. In Enterprise Grid this also drops users whose home workspace is another workspace in the org.
|
|
276
|
+
|
|
255
277
|
When someone mentions the agent partway through a thread, `seedFromThread: true` loads the thread's earlier replies into the new session first, so the agent answers with the context a reader of the thread has. It loads the history once, on the session's first turn, paging through `conversations.replies` and keeping the newest 50 replies (`{ limit }` changes that). Slack limits new commercially distributed apps outside the Marketplace to 15 replies per call and one call a minute, so such an app may only get the thread's first page; internal apps keep the normal limits. A rate limit keeps what was fetched rather than failing the turn.
|
|
256
278
|
|
|
257
279
|
## Messages sent while the agent is working
|
|
@@ -261,7 +283,7 @@ A message sent while the agent is still answering gets 👀 as soon as it arrive
|
|
|
261
283
|
```ts
|
|
262
284
|
export const support = chat.agent({
|
|
263
285
|
id: "support",
|
|
264
|
-
channels: [slack({ id: "slack", endpoint: slackEvents, token })],
|
|
286
|
+
channels: [slack({ id: "slack", endpoint: slackEvents, token, startOn: appMentions() })],
|
|
265
287
|
pendingMessages: { shouldInject: () => true },
|
|
266
288
|
run: async ({ messages, signal, streamText }) =>
|
|
267
289
|
streamText({ model, messages, abortSignal: signal, stopWhen: stepCountIs(10) }),
|
|
@@ -336,6 +358,7 @@ slack({
|
|
|
336
358
|
id: "slack",
|
|
337
359
|
endpoint: slackEvents,
|
|
338
360
|
token,
|
|
361
|
+
startOn: appMentions(),
|
|
339
362
|
delivery: "final",
|
|
340
363
|
outbound: (reply) =>
|
|
341
364
|
reply.text
|
|
@@ -356,13 +379,15 @@ slack({
|
|
|
356
379
|
| `token` | `string` or resolver | Bot token (`xoxb-...`), or a function of the event (its `team_id`) for multi-workspace apps. |
|
|
357
380
|
| `delivery` | `"stream"` or `"final"` | `"stream"` (default) streams the reply natively. `"final"` posts it once when the turn ends. |
|
|
358
381
|
| `title` | `(prompt, event) => string \| null` | The session title, set on its first turn. Defaults to the first line of the prompt. |
|
|
359
|
-
| `progress` | `"tasks"` or `"off"` |
|
|
360
|
-
| `startOn` | filter string |
|
|
382
|
+
| `progress` | `"plan"`, `"tasks"` or `"off"` | How tool calls show in the streamed reply: one plan block (default), a task card per call, or none. Task titles come from each tool's `title`. |
|
|
383
|
+
| `startOn` | filter string | Required. Which channel messages start a session, usually `appMentions()` (see [Channel threads](#channel-threads)). Existing sessions always resume, and the agent's own conversation always starts one. Checked against the endpoint's event type. |
|
|
361
384
|
| `filter` | filter string | Extra [filter](/webhooks/filters) for message events, composed with the built-in guard that drops the bot's own messages. Checked against the endpoint's event type, like `webhook()`'s `filter`. |
|
|
362
385
|
| `key` | `string` | Session [key](/webhooks/session-routing) template. Defaults to one session per thread. |
|
|
363
386
|
| `seedFromThread` | `boolean` or `{ limit }` | Load a thread's earlier replies into a new session. Off by default. |
|
|
364
387
|
| `approval` | `(call, { button }) => message` | The UI for a tool with no `execute`. Defaults to Approve and Deny resolving to `{ approved }`. |
|
|
365
388
|
| `interactions` | `{ [toolName]: (call, { button }) => message }` | A tool's own UI; a click resolves the tool with the button's `output`. |
|
|
366
389
|
| `receivedReaction` | `string \| null` | Emoji added, on arrival, to a message sent while the agent is answering. Default `"eyes"`, null for none. |
|
|
390
|
+
| `errorMessage` | `string \| (ctx) => string` | What the reply shows when a turn fails, fixed or built from the failure ([details](/webhooks/channels#when-a-turn-fails)). Default "Something went wrong answering this. Try again." |
|
|
391
|
+
| `ownWorkspaceOnly` | `boolean` | Drop messages, mentions and button clicks from people outside the app's workspace, such as Slack Connect guests. Off by default. |
|
|
367
392
|
| `inbound` / `outbound` | functions | Map a Slack event to the turn, and a posted reply to a Slack message. |
|
|
368
393
|
| `apiBaseUrl` | `string` | Override the Slack Web API base, for testing against a mock. |
|
|
@@ -4,10 +4,6 @@ description: "Choose an endpoint's source, configure verification, and type the
|
|
|
4
4
|
sidebarTitle: "Sources & verification"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
import WebhooksBetaBanner from "/snippets/webhooks-beta-banner.mdx";
|
|
8
|
-
|
|
9
|
-
<WebhooksBetaBanner />
|
|
10
|
-
|
|
11
7
|
An endpoint's `source` defines how Trigger.dev verifies requests and types the event every subscriber receives. Choose a named provider source or supply a verification configuration with `webhooks.custom()`.
|
|
12
8
|
|
|
13
9
|
## Presets
|
|
@@ -96,16 +92,49 @@ Each function below returns a source with its provider's verification configurat
|
|
|
96
92
|
|
|
97
93
|
| Verification | Source functions |
|
|
98
94
|
| --- | --- |
|
|
99
|
-
| Svix | `clerk()`, `resend()`, `brex()` |
|
|
100
|
-
| Standard Webhooks | `openai()`, `replicate()`, `recallai()`, `composio()` |
|
|
95
|
+
| Svix | `clerk()`, `resend()`, `brex()`, `autumn()` |
|
|
96
|
+
| Standard Webhooks | `openai()`, `replicate()`, `recallai()`, `composio()`, `loops()`, `granola()` |
|
|
101
97
|
| HMAC-SHA256 with `X-Hub-Signature-256` | `whatsapp()` |
|
|
102
98
|
| Shared secret in `X-Gitlab-Token` | `gitlab()` |
|
|
103
|
-
|
|
|
99
|
+
| Shared secret as `Authorization: Bearer` | `supabase()` |
|
|
100
|
+
| Hookdeck relay (HMAC-SHA256 with `X-Hookdeck-Signature`) | `hookdeck()` |
|
|
101
|
+
| Provider-specific HMAC | `calendly()`, `calCom()`, `sentry()`, `pagerduty()`, `vercel()`, `linear()`, `notion()`, `typeform()`, `docusign()`, `jira()`, `intercom()`, `zendesk()`, `attio()`, `workos()`, `elevenlabs()`, `vapi()`, `retell()`, `shopify()`, `metronome()`, `mux()`, `tailscale()`, `langfuse()`, `infisical()`, `planetscale()`, `plain()`, `firecrawl()` |
|
|
104
102
|
|
|
105
103
|
Provider IDs containing a hyphen use a camel case function name: the `cal-com` provider is `webhooks.calCom()`.
|
|
106
104
|
|
|
105
|
+
Supabase database webhooks aren't signed, so `supabase()` checks a shared secret instead. Generate the endpoint's secret on Trigger.dev, then add an `Authorization` header with the value `Bearer <secret>` under the webhook's HTTP Headers in Supabase.
|
|
106
|
+
|
|
107
107
|
Slack also ships as a chat channel in `@trigger.dev/slack`.
|
|
108
108
|
|
|
109
|
+
### Hookdeck
|
|
110
|
+
|
|
111
|
+
`hookdeck()` verifies requests that a [Hookdeck](https://hookdeck.com) connection forwards to the endpoint. Hookdeck forwards the original provider's body unchanged, so pass that provider's event type:
|
|
112
|
+
|
|
113
|
+
```ts /trigger/stripe-via-hookdeck.ts
|
|
114
|
+
import type Stripe from "stripe";
|
|
115
|
+
import { webhook, webhooks } from "@trigger.dev/sdk";
|
|
116
|
+
|
|
117
|
+
export const stripeViaHookdeck = webhooks.endpoint.define({
|
|
118
|
+
id: "stripe-via-hookdeck",
|
|
119
|
+
source: webhooks.hookdeck<Stripe.Event>(),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
export const paymentSucceeded = webhook({
|
|
123
|
+
id: "payment-succeeded",
|
|
124
|
+
endpoint: stripeViaHookdeck,
|
|
125
|
+
filter: "event.type == 'payment_intent.succeeded'",
|
|
126
|
+
onEvent: async ({ event }) => {
|
|
127
|
+
console.log(event.id);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Use the endpoint URL as the connection's HTTP destination, keep the destination's authentication on Hookdeck Signature (the default for new destinations), and store the Hookdeck project's signing secret from **Settings > Secrets** as the endpoint's signing secret.
|
|
133
|
+
|
|
134
|
+
- **Secret rotation:** when you roll the signing secret in Hookdeck with an expiry, Hookdeck also signs each request with the previous secret in `X-Hookdeck-Signature-2`, so deliveries keep verifying until you store the new secret.
|
|
135
|
+
- **Deduplication:** retries dedupe on `X-Hookdeck-Eventid`, which is the same on every attempt of a Hookdeck event.
|
|
136
|
+
- **Custom header prefix:** if you changed the header prefix in the Hookdeck project's settings, pass it with `webhooks.hookdeck({ headerPrefix: "x-acme" })`. The prefix renames the event id header; the signature headers keep the `X-Hookdeck-` name.
|
|
137
|
+
|
|
109
138
|
## Endpoint validation
|
|
110
139
|
|
|
111
140
|
Some sources also define a response for the provider's endpoint-validation request. These requests don't create deliveries or task runs.
|
|
@@ -4,10 +4,6 @@ description: "Pause a run until a matching webhook arrives on an endpoint, then
|
|
|
4
4
|
sidebarTitle: "Waiters"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
import WebhooksBetaBanner from "/snippets/webhooks-beta-banner.mdx";
|
|
8
|
-
|
|
9
|
-
<WebhooksBetaBanner />
|
|
10
|
-
|
|
11
7
|
**A waiter pauses a run until a matching delivery arrives on an [endpoint](/webhooks/overview).** Use it when your task starts something in another system and needs to know when that system reports back: a payment that settles, a build that finishes, a document that gets signed.
|
|
12
8
|
|
|
13
9
|
```ts /trigger/checkout.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trigger.dev/sdk",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.1",
|
|
4
4
|
"description": "trigger.dev Node.JS SDK",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@opentelemetry/api": "1.9.1",
|
|
71
71
|
"@opentelemetry/semantic-conventions": "1.41.1",
|
|
72
|
-
"@trigger.dev/core": "4.8.
|
|
72
|
+
"@trigger.dev/core": "4.8.1",
|
|
73
73
|
"uncrypto": "^0.1.3"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|