@trigger.dev/sdk 4.8.0-webhooks.20261015065339 → 4.8.0

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.
Files changed (53) hide show
  1. package/dist/commonjs/v3/ai.d.ts +69 -2
  2. package/dist/commonjs/v3/ai.js +120 -30
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/query.d.ts +434 -1
  5. package/dist/commonjs/v3/query.js +115 -0
  6. package/dist/commonjs/v3/query.js.map +1 -1
  7. package/dist/commonjs/v3/webhooks.d.ts +19 -5
  8. package/dist/commonjs/v3/webhooks.js +19 -2
  9. package/dist/commonjs/v3/webhooks.js.map +1 -1
  10. package/dist/commonjs/version.js +1 -1
  11. package/dist/esm/v3/ai.d.ts +69 -2
  12. package/dist/esm/v3/ai.js +119 -29
  13. package/dist/esm/v3/ai.js.map +1 -1
  14. package/dist/esm/v3/query.d.ts +434 -1
  15. package/dist/esm/v3/query.js +115 -0
  16. package/dist/esm/v3/query.js.map +1 -1
  17. package/dist/esm/v3/webhooks.d.ts +19 -5
  18. package/dist/esm/v3/webhooks.js +19 -2
  19. package/dist/esm/v3/webhooks.js.map +1 -1
  20. package/dist/esm/version.js +1 -1
  21. package/docs/ai-chat/backend.mdx +12 -3
  22. package/docs/ai-chat/client-protocol.mdx +6 -3
  23. package/docs/ai-chat/patterns/redacting-telemetry.mdx +117 -0
  24. package/docs/ai-chat/reference.mdx +3 -0
  25. package/docs/ai-chat/transcript-storage.mdx +1 -1
  26. package/docs/apikeys.mdx +6 -4
  27. package/docs/cli-runs-commands.mdx +2 -2
  28. package/docs/cli-tasks-commands.mdx +95 -0
  29. package/docs/config/config-file.mdx +58 -0
  30. package/docs/config/extensions/prismaExtension.mdx +4 -4
  31. package/docs/deploy-environment-variables.mdx +23 -0
  32. package/docs/deployment/atomic-deployment.mdx +3 -3
  33. package/docs/deployment/overview.mdx +1 -1
  34. package/docs/deployment/preview-branches.mdx +1 -1
  35. package/docs/github-actions.mdx +3 -3
  36. package/docs/limits.mdx +6 -0
  37. package/docs/manual-setup.mdx +1 -1
  38. package/docs/mcp-tools.mdx +19 -3
  39. package/docs/observability/dashboards.mdx +62 -0
  40. package/docs/runs/max-duration.mdx +4 -0
  41. package/docs/self-hosting/env/supervisor.mdx +1 -0
  42. package/docs/webhooks/channels.mdx +31 -5
  43. package/docs/webhooks/connect.mdx +14 -3
  44. package/docs/webhooks/deliveries.mdx +8 -1
  45. package/docs/webhooks/filters.mdx +39 -2
  46. package/docs/webhooks/human-in-the-loop.mdx +11 -2
  47. package/docs/webhooks/linear.mdx +5 -4
  48. package/docs/webhooks/overview.mdx +16 -0
  49. package/docs/webhooks/session-routing.mdx +11 -2
  50. package/docs/webhooks/slack.mdx +51 -18
  51. package/docs/webhooks/sources.mdx +36 -3
  52. package/docs/webhooks/waiters.mdx +22 -2
  53. package/package.json +3 -2
@@ -1,6 +1,6 @@
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 task cards, asks for approvals, and stops on Slack's stop button."
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
 
@@ -9,14 +9,21 @@ sidebarTitle: "Slack agents"
9
9
  ```ts /trigger/support.ts
10
10
  import { webhooks } from "@trigger.dev/sdk";
11
11
  import { chat } from "@trigger.dev/sdk/ai";
12
- import { slack, webhookSource } from "@trigger.dev/slack";
12
+ import { appMentions, slack, webhookSource } from "@trigger.dev/slack";
13
13
  import { anthropic } from "@ai-sdk/anthropic";
14
14
 
15
15
  const slackEvents = webhooks.endpoint.define({ id: "slack", source: webhookSource() });
16
16
 
17
17
  export const support = chat.agent({
18
18
  id: "support",
19
- channels: [slack({ id: "slack", endpoint: slackEvents, token: process.env.SLACK_BOT_TOKEN! })],
19
+ channels: [
20
+ slack({
21
+ id: "slack",
22
+ endpoint: slackEvents,
23
+ token: process.env.SLACK_BOT_TOKEN!,
24
+ startOn: appMentions(),
25
+ }),
26
+ ],
20
27
  tools: { searchDocs, refundOrder },
21
28
  run: async ({ messages, tools, signal, streamText }) =>
22
29
  streamText({ model: anthropic("claude-sonnet-4-5"), messages, tools, abortSignal: signal }),
@@ -54,6 +61,7 @@ export const support = chat.agent({
54
61
  - files:read
55
62
  - reactions:write
56
63
  - im:history
64
+ - app_mentions:read
57
65
  - channels:history
58
66
  - groups:history
59
67
  settings:
@@ -61,6 +69,7 @@ export const support = chat.agent({
61
69
  request_url: https://your-endpoint-url
62
70
  bot_events:
63
71
  - message.im
72
+ - app_mention
64
73
  - message.channels
65
74
  - message.groups
66
75
  - agent_session_stopped
@@ -69,7 +78,7 @@ export const support = chat.agent({
69
78
  request_url: https://your-endpoint-url
70
79
  ```
71
80
 
72
- `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, and `reactions:write` lets it mark a message it picked up mid-reply. Drop `message.channels`, `message.groups` and their `history` scopes if the agent should only answer in its own conversations.
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.
73
82
  </Step>
74
83
  <Step title="Connect the endpoint">
75
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.
@@ -85,18 +94,20 @@ export const support = chat.agent({
85
94
  | --- | --- |
86
95
  | A turn starts | The session's status is **processing**. On the first turn the session is titled with the prompt. |
87
96
  | The model writes | The reply streams into the thread with `chat.startStream`, `chat.appendStream` and `chat.stopStream`, as markdown. |
88
- | A tool is called | A task card in the reply, marked complete when the tool returns and failed if it throws. |
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. |
89
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. |
90
100
  | The turn ends | The status goes back to **active**, whether it answered, stopped, failed or had nothing to say. |
91
101
  | `chat.close()` | The status is **closed**. |
92
102
 
93
- 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 task cards, and `delivery: "final"` posts the whole reply once the turn ends instead of streaming it.
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.
94
104
 
95
105
  ```ts
96
106
  slack({
97
107
  id: "slack",
98
108
  endpoint: slackEvents,
99
109
  token,
110
+ startOn: appMentions(),
100
111
  title: (prompt) => `Support: ${prompt.slice(0, 80)}`,
101
112
  });
102
113
  ```
@@ -114,6 +125,7 @@ slack({
114
125
  id: "slack",
115
126
  endpoint: slackEvents,
116
127
  token,
128
+ startOn: appMentions(),
117
129
  approval: (call, { button }) => ({
118
130
  text: `Run ${call.toolName}?`,
119
131
  blocks: [
@@ -140,6 +152,7 @@ export const moji = chat.agent({
140
152
  id: "slack",
141
153
  endpoint: slackEvents,
142
154
  token,
155
+ startOn: appMentions(),
143
156
  interactions: {
144
157
  pickEmoji: (call, { button }) => {
145
158
  const { options } = call.input as { options: { name: string }[] };
@@ -177,18 +190,18 @@ The click resolves `pickEmoji` to `{ picked: "wizard" }`, the buttons collapse t
177
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:
178
191
 
179
192
  ```ts
180
- import { slackButton, type SlackSurface } from "@trigger.dev/slack";
193
+ import { slackButton, type SlackRunSurface } from "@trigger.dev/slack";
181
194
 
182
195
  export const support = chat.agent({
183
196
  id: "support",
184
- channels: [slack({ id: "slack", endpoint: slackEvents, token })],
197
+ channels: [slack({ id: "slack", endpoint: slackEvents, token, startOn: appMentions() })],
185
198
  actionSchema: z.object({ type: z.literal("ask"), question: z.string() }),
186
199
  tools: {
187
200
  offerFollowUps: tool({
188
201
  description: "Offer the user up to three follow-up questions as buttons",
189
202
  inputSchema: z.object({ questions: z.array(z.string()).max(3) }),
190
203
  execute: async ({ questions }) => {
191
- await chat.surface<SlackSurface>()?.post({
204
+ await chat.surface<SlackRunSurface>()?.post({
192
205
  text: "Follow-up questions",
193
206
  blocks: [
194
207
  {
@@ -209,11 +222,15 @@ export const support = chat.agent({
209
222
  });
210
223
  ```
211
224
 
212
- `onAction` also gets the click itself as `channel.event`, typed with `SlackBlockActions`: who clicked, and the message the button was on. An action doesn't have to answer; return nothing to only do work:
225
+ `onAction` also gets the click itself as `channel.event`, typed with `SlackBlockActions`: who clicked, and the message the button was on. An action doesn't have to answer; return nothing to only do work. Here `actionSchema` accepts a `pick` action alongside `ask`:
213
226
 
214
227
  ```ts
215
228
  import type { SlackBlockActions } from "@trigger.dev/slack";
216
229
 
230
+ actionSchema: z.discriminatedUnion("type", [
231
+ z.object({ type: z.literal("ask"), question: z.string() }),
232
+ z.object({ type: z.literal("pick"), name: z.string() }),
233
+ ]),
217
234
  onAction: async ({ action, channel }) => {
218
235
  const click = channel?.event as SlackBlockActions | undefined;
219
236
  if (action.type === "pick") {
@@ -236,15 +253,28 @@ Slack shows a stop button while the agent is working. It aborts the running turn
236
253
 
237
254
  ## Channel threads
238
255
 
239
- In a channel the agent answers every thread it can see. To have it answer only when it is @mentioned, pass `startOn` with the `mentions` helper and your bot's user id. 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.
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.
240
257
 
241
258
  ```ts
242
- import { slack, mentions } from "@trigger.dev/slack";
259
+ import { slack, appMentions } from "@trigger.dev/slack";
243
260
 
244
- slack({ id: "slack", endpoint: slackEvents, token, startOn: mentions("U012BOT") });
261
+ slack({ id: "slack", endpoint: slackEvents, token, startOn: appMentions() });
245
262
  ```
246
263
 
247
- 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 reads `conversations.replies` once, on the session's first turn, and keeps the newest 50 replies (`{ limit }` changes that). New non-Marketplace apps are limited by Slack to 15 replies per call and one call a minute; a rate limit keeps what was fetched rather than failing the turn.
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
+
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.
248
278
 
249
279
  ## Messages sent while the agent is working
250
280
 
@@ -253,7 +283,7 @@ A message sent while the agent is still answering gets 👀 as soon as it arrive
253
283
  ```ts
254
284
  export const support = chat.agent({
255
285
  id: "support",
256
- channels: [slack({ id: "slack", endpoint: slackEvents, token })],
286
+ channels: [slack({ id: "slack", endpoint: slackEvents, token, startOn: appMentions() })],
257
287
  pendingMessages: { shouldInject: () => true },
258
288
  run: async ({ messages, signal, streamText }) =>
259
289
  streamText({ model, messages, abortSignal: signal, stopWhen: stepCountIs(10) }),
@@ -328,6 +358,7 @@ slack({
328
358
  id: "slack",
329
359
  endpoint: slackEvents,
330
360
  token,
361
+ startOn: appMentions(),
331
362
  delivery: "final",
332
363
  outbound: (reply) =>
333
364
  reply.text
@@ -348,13 +379,15 @@ slack({
348
379
  | `token` | `string` or resolver | Bot token (`xoxb-...`), or a function of the event (its `team_id`) for multi-workspace apps. |
349
380
  | `delivery` | `"stream"` or `"final"` | `"stream"` (default) streams the reply natively. `"final"` posts it once when the turn ends. |
350
381
  | `title` | `(prompt, event) => string \| null` | The session title, set on its first turn. Defaults to the first line of the prompt. |
351
- | `progress` | `"tasks"` or `"off"` | Tool calls as task cards in the streamed reply. Default `"tasks"`. |
352
- | `startOn` | `string` | Only start a session in a channel when the event matches (see `mentions`). Existing sessions always resume, and the agent's own conversation always starts one. |
353
- | `filter` | `string` | Extra [filter](/webhooks/filters) for message events, composed with the built-in guard that drops the bot's own messages. |
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. |
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`. |
354
385
  | `key` | `string` | Session [key](/webhooks/session-routing) template. Defaults to one session per thread. |
355
386
  | `seedFromThread` | `boolean` or `{ limit }` | Load a thread's earlier replies into a new session. Off by default. |
356
387
  | `approval` | `(call, { button }) => message` | The UI for a tool with no `execute`. Defaults to Approve and Deny resolving to `{ approved }`. |
357
388
  | `interactions` | `{ [toolName]: (call, { button }) => message }` | A tool's own UI; a click resolves the tool with the button's `output`. |
358
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. |
359
392
  | `inbound` / `outbound` | functions | Map a Slack event to the turn, and a posted reply to a Slack message. |
360
393
  | `apiBaseUrl` | `string` | Override the Slack Web API base, for testing against a mock. |
@@ -92,16 +92,49 @@ Each function below returns a source with its provider's verification configurat
92
92
 
93
93
  | Verification | Source functions |
94
94
  | --- | --- |
95
- | Svix | `clerk()`, `resend()`, `brex()` |
96
- | Standard Webhooks | `openai()`, `replicate()`, `recallai()`, `composio()` |
95
+ | Svix | `clerk()`, `resend()`, `brex()`, `autumn()` |
96
+ | Standard Webhooks | `openai()`, `replicate()`, `recallai()`, `composio()`, `loops()`, `granola()` |
97
97
  | HMAC-SHA256 with `X-Hub-Signature-256` | `whatsapp()` |
98
98
  | Shared secret in `X-Gitlab-Token` | `gitlab()` |
99
- | Provider-specific HMAC | `calendly()`, `calCom()`, `sentry()`, `pagerduty()`, `vercel()`, `linear()`, `notion()`, `typeform()`, `docusign()`, `jira()`, `intercom()`, `zendesk()`, `attio()`, `workos()`, `elevenlabs()`, `vapi()`, `retell()`, `shopify()` |
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()` |
100
102
 
101
103
  Provider IDs containing a hyphen use a camel case function name: the `cal-com` provider is `webhooks.calCom()`.
102
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
+
103
107
  Slack also ships as a chat channel in `@trigger.dev/slack`.
104
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
+
105
138
  ## Endpoint validation
106
139
 
107
140
  Some sources also define a response for the provider's endpoint-validation request. These requests don't create deliveries or task runs.
@@ -12,7 +12,15 @@ import { stripe } from "./stripe"; // webhooks.endpoint.define({ id: "stripe", s
12
12
 
13
13
  export const checkout = task({
14
14
  id: "checkout",
15
- run: async ({ orderId }: { orderId: string }) => {
15
+ run: async ({
16
+ orderId,
17
+ customerId,
18
+ paymentMethodId,
19
+ }: {
20
+ orderId: string;
21
+ customerId: string;
22
+ paymentMethodId: string;
23
+ }) => {
16
24
  const waiter = await webhooks.waiter.create(stripe, {
17
25
  match: {
18
26
  "event.type": "payment_intent.succeeded",
@@ -23,7 +31,19 @@ export const checkout = task({
23
31
  idempotencyKey: `payment-${orderId}`,
24
32
  });
25
33
 
26
- await stripeClient.paymentIntents.create({ amount: 4200, currency: "usd", metadata: { orderId } });
34
+ // confirm: true charges the customer's saved payment method now, so Stripe sends payment_intent.succeeded.
35
+ // Stripe's own idempotency key stops a retry from creating a second intent for the order.
36
+ await stripeClient.paymentIntents.create(
37
+ {
38
+ amount: 4200,
39
+ currency: "usd",
40
+ customer: customerId,
41
+ payment_method: paymentMethodId,
42
+ confirm: true,
43
+ metadata: { orderId },
44
+ },
45
+ { idempotencyKey: `payment-intent-${orderId}` }
46
+ );
27
47
 
28
48
  const { event, deliveryId } = await webhooks.waiter.wait(waiter).unwrap();
29
49
  return { paymentIntent: event.data.object.id, deliveryId };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trigger.dev/sdk",
3
- "version": "4.8.0-webhooks.20261015065339",
3
+ "version": "4.8.0",
4
4
  "description": "trigger.dev Node.JS SDK",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -69,12 +69,13 @@
69
69
  "dependencies": {
70
70
  "@opentelemetry/api": "1.9.1",
71
71
  "@opentelemetry/semantic-conventions": "1.41.1",
72
- "@trigger.dev/core": "4.8.0-webhooks.20261015065339",
72
+ "@trigger.dev/core": "4.8.0",
73
73
  "uncrypto": "^0.1.3"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@ai-sdk/provider": "3.0.8",
77
77
  "@arethetypeswrong/cli": "^0.18.5",
78
+ "@opentelemetry/sdk-trace-base": "2.8.0",
78
79
  "@types/react": "^19.2.14",
79
80
  "ai": "^6.0.116",
80
81
  "ai-v7": "npm:ai@7.0.0-canary.159",