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

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 (154) hide show
  1. package/dist/commonjs/v3/ai.d.ts +560 -8
  2. package/dist/commonjs/v3/ai.js +2263 -178
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/chat.d.ts +23 -0
  5. package/dist/commonjs/v3/chat.js.map +1 -1
  6. package/dist/commonjs/v3/concurrencyLimits.d.ts +37 -7
  7. package/dist/commonjs/v3/concurrencyLimits.js +38 -70
  8. package/dist/commonjs/v3/concurrencyLimits.js.map +1 -1
  9. package/dist/commonjs/v3/index.d.ts +3 -1
  10. package/dist/commonjs/v3/index.js +23 -17
  11. package/dist/commonjs/v3/index.js.map +1 -1
  12. package/dist/commonjs/v3/modelFileDownload.d.ts +10 -0
  13. package/dist/commonjs/v3/modelFileDownload.js +164 -0
  14. package/dist/commonjs/v3/modelFileDownload.js.map +1 -0
  15. package/dist/commonjs/v3/runs.d.ts +22 -1
  16. package/dist/commonjs/v3/runs.js +6 -3
  17. package/dist/commonjs/v3/runs.js.map +1 -1
  18. package/dist/commonjs/v3/test/index.d.ts +2 -1
  19. package/dist/commonjs/v3/test/index.js +4 -1
  20. package/dist/commonjs/v3/test/index.js.map +1 -1
  21. package/dist/commonjs/v3/test/mock-channel-connector.d.ts +172 -0
  22. package/dist/commonjs/v3/test/mock-channel-connector.js +173 -0
  23. package/dist/commonjs/v3/test/mock-channel-connector.js.map +1 -0
  24. package/dist/commonjs/v3/test/mock-chat-agent.d.ts +81 -1
  25. package/dist/commonjs/v3/test/mock-chat-agent.js +137 -10
  26. package/dist/commonjs/v3/test/mock-chat-agent.js.map +1 -1
  27. package/dist/commonjs/v3/transcriptStorage.d.ts +44 -1
  28. package/dist/commonjs/v3/transcriptStorage.js +24 -0
  29. package/dist/commonjs/v3/transcriptStorage.js.map +1 -1
  30. package/dist/commonjs/v3/webhookWaiters.d.ts +80 -0
  31. package/dist/commonjs/v3/webhookWaiters.js +156 -0
  32. package/dist/commonjs/v3/webhookWaiters.js.map +1 -0
  33. package/dist/commonjs/v3/webhooks.d.ts +178 -2
  34. package/dist/commonjs/v3/webhooks.js +322 -1
  35. package/dist/commonjs/v3/webhooks.js.map +1 -1
  36. package/dist/commonjs/version.js +1 -1
  37. package/dist/esm/v3/ai.d.ts +560 -8
  38. package/dist/esm/v3/ai.js +2257 -178
  39. package/dist/esm/v3/ai.js.map +1 -1
  40. package/dist/esm/v3/chat.d.ts +23 -0
  41. package/dist/esm/v3/chat.js.map +1 -1
  42. package/dist/esm/v3/concurrencyLimits.d.ts +37 -7
  43. package/dist/esm/v3/concurrencyLimits.js +38 -70
  44. package/dist/esm/v3/concurrencyLimits.js.map +1 -1
  45. package/dist/esm/v3/index.d.ts +3 -1
  46. package/dist/esm/v3/index.js +1 -0
  47. package/dist/esm/v3/index.js.map +1 -1
  48. package/dist/esm/v3/modelFileDownload.d.ts +10 -0
  49. package/dist/esm/v3/modelFileDownload.js +161 -0
  50. package/dist/esm/v3/modelFileDownload.js.map +1 -0
  51. package/dist/esm/v3/runs.d.ts +22 -1
  52. package/dist/esm/v3/runs.js +6 -3
  53. package/dist/esm/v3/runs.js.map +1 -1
  54. package/dist/esm/v3/test/index.d.ts +2 -1
  55. package/dist/esm/v3/test/index.js +2 -1
  56. package/dist/esm/v3/test/index.js.map +1 -1
  57. package/dist/esm/v3/test/mock-channel-connector.d.ts +172 -0
  58. package/dist/esm/v3/test/mock-channel-connector.js +170 -0
  59. package/dist/esm/v3/test/mock-channel-connector.js.map +1 -0
  60. package/dist/esm/v3/test/mock-chat-agent.d.ts +81 -1
  61. package/dist/esm/v3/test/mock-chat-agent.js +137 -11
  62. package/dist/esm/v3/test/mock-chat-agent.js.map +1 -1
  63. package/dist/esm/v3/transcriptStorage.d.ts +44 -1
  64. package/dist/esm/v3/transcriptStorage.js +24 -0
  65. package/dist/esm/v3/transcriptStorage.js.map +1 -1
  66. package/dist/esm/v3/webhookWaiters.d.ts +80 -0
  67. package/dist/esm/v3/webhookWaiters.js +149 -0
  68. package/dist/esm/v3/webhookWaiters.js.map +1 -0
  69. package/dist/esm/v3/webhooks.d.ts +178 -2
  70. package/dist/esm/v3/webhooks.js +318 -1
  71. package/dist/esm/v3/webhooks.js.map +1 -1
  72. package/dist/esm/version.js +1 -1
  73. package/docs/ai-chat/actions.mdx +6 -1
  74. package/docs/ai-chat/backend.mdx +34 -0
  75. package/docs/ai-chat/patterns/sub-agents.mdx +33 -0
  76. package/docs/ai-chat/pending-messages.mdx +2 -0
  77. package/docs/ai-chat/reference.mdx +50 -2
  78. package/docs/apikeys.mdx +3 -3
  79. package/docs/billing-limits.mdx +1 -1
  80. package/docs/cli-env-commands.mdx +5 -4
  81. package/docs/cli-init-commands.mdx +27 -1
  82. package/docs/cli-login-commands.mdx +20 -0
  83. package/docs/cli-projects-commands.mdx +13 -1
  84. package/docs/cli-runs-commands.mdx +3 -2
  85. package/docs/cli-update-commands.mdx +9 -3
  86. package/docs/cli-whoami-commands.mdx +12 -0
  87. package/docs/concurrency.mdx +32 -0
  88. package/docs/config/extensions/audioWaveform.mdx +4 -1
  89. package/docs/config/extensions/overview.mdx +2 -0
  90. package/docs/config/extensions/prismaExtension.mdx +2 -3
  91. package/docs/config/extensions/puppeteer.mdx +3 -5
  92. package/docs/config/extensions/pythonExtension.mdx +9 -1
  93. package/docs/config/extensions/syncEnvVars.mdx +6 -0
  94. package/docs/deploy-environment-variables.mdx +1 -1
  95. package/docs/deployment/atomic-deployment.mdx +2 -0
  96. package/docs/deployment/dev-branches.mdx +5 -5
  97. package/docs/deployment/overview.mdx +8 -7
  98. package/docs/deployment/preview-branches.mdx +6 -3
  99. package/docs/errors-retrying.mdx +4 -4
  100. package/docs/how-it-works.mdx +5 -5
  101. package/docs/introduction.mdx +6 -0
  102. package/docs/limits.mdx +26 -8
  103. package/docs/logging.mdx +1 -1
  104. package/docs/machines.mdx +1 -1
  105. package/docs/mcp-introduction.mdx +8 -7
  106. package/docs/mcp-tools.mdx +91 -1
  107. package/docs/migrating-from-v3.mdx +1 -1
  108. package/docs/observability/dashboards.mdx +2 -0
  109. package/docs/observability/query.mdx +6 -2
  110. package/docs/private-networking/aws-console-setup.mdx +1 -1
  111. package/docs/private-networking/overview.mdx +1 -1
  112. package/docs/quick-start.mdx +3 -40
  113. package/docs/realtime/auth.mdx +1 -1
  114. package/docs/realtime/backend/input-streams.mdx +159 -0
  115. package/docs/realtime/backend/streams.mdx +25 -10
  116. package/docs/realtime/how-it-works.mdx +1 -1
  117. package/docs/realtime/react-hooks/overview.mdx +1 -1
  118. package/docs/realtime/react-hooks/streams.mdx +2 -2
  119. package/docs/realtime/react-hooks/subscribe.mdx +19 -3
  120. package/docs/realtime/react-hooks/triggering.mdx +2 -2
  121. package/docs/realtime/react-hooks/use-wait-token.mdx +1 -1
  122. package/docs/realtime/run-object.mdx +47 -24
  123. package/docs/replaying.mdx +26 -1
  124. package/docs/reports.mdx +4 -2
  125. package/docs/run-tests.mdx +2 -2
  126. package/docs/run-usage.mdx +3 -3
  127. package/docs/runs/heartbeats.mdx +2 -2
  128. package/docs/runs/list-best-practices.mdx +234 -0
  129. package/docs/runs/metadata.mdx +7 -10
  130. package/docs/runs.mdx +18 -8
  131. package/docs/self-hosting/env/supervisor.mdx +2 -0
  132. package/docs/self-hosting/env/webapp.mdx +5 -0
  133. package/docs/self-hosting/overview.mdx +26 -0
  134. package/docs/tags.mdx +6 -0
  135. package/docs/tasks/scheduled.mdx +11 -5
  136. package/docs/triggering.mdx +8 -0
  137. package/docs/troubleshooting-alerts.mdx +102 -3
  138. package/docs/troubleshooting.mdx +1 -1
  139. package/docs/upgrading-packages.mdx +3 -3
  140. package/docs/vercel-integration.mdx +13 -3
  141. package/docs/versioning.mdx +1 -1
  142. package/docs/wait-for-token.mdx +3 -3
  143. package/docs/webhooks/channels.mdx +128 -0
  144. package/docs/webhooks/connect.mdx +80 -0
  145. package/docs/webhooks/deliveries.mdx +92 -0
  146. package/docs/webhooks/filters.mdx +109 -0
  147. package/docs/webhooks/human-in-the-loop.mdx +149 -0
  148. package/docs/webhooks/linear.mdx +185 -0
  149. package/docs/webhooks/overview.mdx +99 -0
  150. package/docs/webhooks/session-routing.mdx +132 -0
  151. package/docs/webhooks/slack.mdx +360 -0
  152. package/docs/webhooks/sources.mdx +217 -0
  153. package/docs/webhooks/waiters.mdx +170 -0
  154. package/package.json +3 -2
@@ -14,6 +14,7 @@ mode: "wide"
14
14
  | `OTEL_EXPORTER_OTLP_ENDPOINT` | Yes | — | OTel exporter endpoint. Point at: `<webapp-url>/otel` |
15
15
  | **Worker instance** | | | |
16
16
  | `TRIGGER_WORKER_INSTANCE_NAME` | No | random UUID | Worker instance name. Set to `spec.nodeName` on k8s. |
17
+ | `TRIGGER_WORKER_CELL_NAME` | No | — | Name of the worker cluster this supervisor serves. Stamped on each run it starts. |
17
18
  | `TRIGGER_WORKER_HEARTBEAT_INTERVAL_SECONDS` | No | 30 | Worker heartbeat interval (seconds). |
18
19
  | **Workload API settings** | | | |
19
20
  | `TRIGGER_WORKLOAD_API_ENABLED` | No | true | Enable workload API. Runs use this to perform actions. |
@@ -63,6 +64,7 @@ mode: "wide"
63
64
  | **Failed pod handler** | | | |
64
65
  | `FAILED_POD_HANDLER_ENABLED` | No | true | Enable failed pod handler. |
65
66
  | `FAILED_POD_HANDLER_RECONNECT_INTERVAL_MS` | No | 1000 (1s) | Failed pod handler reconnect interval (ms). |
67
+ | `FAILED_POD_HANDLER_WATCH_TIMEOUT_SECONDS` | No | 300 (5m) | How long the server keeps each failed pod watch open (s). |
66
68
  | **Debug** | | | |
67
69
  | `DEBUG` | No | false | Enable debug logs. |
68
70
  | `SEND_RUN_DEBUG_LOGS` | No | false | Send run debug logs to the platform. |
@@ -44,6 +44,9 @@ mode: "wide"
44
44
  | `WHITELISTED_EMAILS` | No | — | Whitelisted emails regex. |
45
45
  | `BLOCKED_EMAIL_DOMAINS` | No | — | Comma-separated email domains that can't sign in or sign up, including their subdomains. |
46
46
  | `LOGIN_RATE_LIMITS_ENABLED` | No | true | Enable rate limiting on magic-link login. |
47
+ | `MAGIC_LINK_SAME_BROWSER_REQUIRED` | No | true | Only accept a magic link opened in the browser that requested it. Set to false to allow opening it on another device.|
48
+ | `TURNSTILE_SITE_KEY` | No | — | Cloudflare Turnstile site key. With `TURNSTILE_SECRET_KEY`, adds a bot check to the magic-link sign-in form. |
49
+ | `TURNSTILE_SECRET_KEY` | No | — | Cloudflare Turnstile secret key, used to verify the check on the server. Turnstile is off unless both keys are set.|
47
50
  | `AUTH_GITHUB_CLIENT_ID` | No | — | GitHub client ID. |
48
51
  | `AUTH_GITHUB_CLIENT_SECRET` | No | — | GitHub client secret. |
49
52
  | `CSP_IMG_SRC_ALLOWLIST` | No | — | Extra hosts the dashboard may load images from, e.g. an SSO or avatar host serving profile images. A comma-separated list of exact origins (`https://sso.example.com`); a wildcard host such as `https://*.example.com` is refused. |
@@ -98,6 +101,8 @@ mode: "wide"
98
101
  | `DEPLOY_REGISTRY_NAMESPACE` | No | trigger | Deploy registry namespace. |
99
102
  | `DEPLOY_REGISTRY_ECR_DEFAULT_REPOSITORY_POLICY` | No | — | Raw IAM policy JSON applied via SetRepositoryPolicy to every ECR repo created by the webapp. Use to grant cross-account pull access to EKS workers when the ECR account is separate from the cluster account. |
100
103
  | `DEPLOY_IMAGE_PLATFORM` | No | linux/amd64 | Deploy image platform, same values as docker `--platform` flag. |
104
+ | `DEPLOY_BASE_IMAGES` | No | — | Base images every deploy must build on, as comma-separated `runtime=image@sha256:<digest>`. See [custom base images](/self-hosting/overview#custom-base-images). |
105
+ | `DEPLOY_BUILD_BASE_IMAGES` | No | — | Build-stage images, in the same format as `DEPLOY_BASE_IMAGES`. See [custom base images](/self-hosting/overview#custom-base-images). |
101
106
  | `DEPLOY_TIMEOUT_MS` | No | 480000 (8m) | Deploy timeout (ms). |
102
107
  | `DEPLOY_QUEUE_TIMEOUT_MS` | No | 900000 (15m) | Deploy queue timeout (ms). |
103
108
  | **Object store (S3)** | | | |
@@ -100,6 +100,32 @@ All fields are optional. Partial overrides are supported:
100
100
  }
101
101
  ```
102
102
 
103
+ ## Custom base images
104
+
105
+ Deploys build on the published `triggerdotdev/node` and `triggerdotdev/bun` Debian images. To require a different base for every deploy to your instance, such as a FIPS-validated or hardened Node image, set `DEPLOY_BASE_IMAGES` on the webapp (and optionally `DEPLOY_BUILD_BASE_IMAGES` for the build stage):
106
+
107
+ ```bash
108
+ DEPLOY_BASE_IMAGES="node-26=registry.example.com/node-fips:26@sha256:<64-character-digest>"
109
+ ```
110
+
111
+ Entries are comma-separated `runtime=image@sha256:<digest>`. The runtimes are `node-22`, `node-24`, `node-26` and `bun`, and every image must be pinned by digest. An invalid value prevents the webapp from starting. Projects with `runtime: "node"` in their config resolve to the current default Node runtime (`node-24` today), so set that key for them. With the Helm chart, set the variables through `webapp.extraEnvVars`.
112
+
113
+ Runtimes with an entry build on that image; the others keep the published images. Deploys from CLI versions that cannot apply the images are rejected with an error asking to upgrade, and so are `--native-build`, `--local-bundle` and `--from-bundle` deploys. This is a self-hosting setting and does not apply to Trigger.dev Cloud.
114
+
115
+ You own a custom base image. It must provide:
116
+
117
+ - `node` on `PATH` at the runtime's major version (for `bun`, both `bun` and `node`, because the final stage starts the app with `dumb-init node`)
118
+ - `busybox`, `ca-certificates`, `dumb-init`, `git` and `openssl`
119
+ - a `node` user (a `bun` user for the Bun runtime)
120
+ - glibc, so native modules built in the build stage load at runtime
121
+
122
+ A `DEPLOY_BUILD_BASE_IMAGES` image needs everything the base image provides, plus `python3`, `make` and `g++`. Build extensions that add image instructions are replayed on it. Without an entry, the build stage uses the published build image, except for projects whose build extensions add image instructions: those build from your base image and install the toolchain with `apt-get`, so that base must be Debian-based. To avoid the published images entirely, set both variables.
123
+
124
+ <Warning>
125
+ `image.pkgs` and build extensions that run `apt-get` (such as `aptGet` and `playwright`) assume a
126
+ Debian base. On other distributions, install those packages in your base image instead.
127
+ </Warning>
128
+
103
129
  ## Community support
104
130
 
105
131
  It's dangerous to go alone! Join the self-hosting channel on our [Discord server](https://discord.gg/NQTxt5NA7s).
package/docs/tags.mdx CHANGED
@@ -118,3 +118,9 @@ for await (const run of runs.list({ tag: "user_123456", status: ["COMPLETED"] })
118
118
  console.log(run.id, run.taskIdentifier, run.finishedAt, run.tags);
119
119
  }
120
120
  ```
121
+
122
+ <Tip>
123
+ To follow runs with a tag as they change, use
124
+ [`runs.subscribeToRunsWithTag()`](/realtime/backend/subscribe) instead of polling `runs.list()`.
125
+ See [runs.list() best practices](/runs/list-best-practices).
126
+ </Tip>
@@ -45,7 +45,7 @@ export const firstScheduledTask = schedules.task({
45
45
  //this can be undefined if you didn't provide one
46
46
  console.log(payload.externalId); //is a string or undefined
47
47
 
48
- //the next 5 dates this task is scheduled to run
48
+ //the next 10 dates this task is scheduled to run
49
49
  console.log(payload.upcoming); //is an array of Date objects
50
50
  },
51
51
  });
@@ -58,14 +58,14 @@ You can see from the comments that the payload has several useful properties:
58
58
  - `timezone` - the timezone the schedule was registered with, defaults to "UTC". In IANA format, e.g. "America/New_York".
59
59
  - `scheduleId` - the id of the schedule that triggered the task
60
60
  - `externalId` - the external id you (optionally) provided when creating the schedule
61
- - `upcoming` - the next 5 times the task is scheduled to run
61
+ - `upcoming` - the next 10 times the task is scheduled to run
62
62
 
63
63
  <Note>
64
64
  This task will NOT get triggered on a schedule until you attach a schedule to it. Read on for how
65
65
  to do that.
66
66
  </Note>
67
67
 
68
- Like all tasks they don't have timeouts, they should be placed inside a [/trigger folder](/config/config-file), and you [can configure them](/tasks/overview#defining-a-task).
68
+ Like all tasks they have a [maxDuration](/runs/max-duration), they should be placed inside a [/trigger folder](/config/config-file), and you [can configure them](/tasks/overview#defining-a-task).
69
69
 
70
70
  You can set a [TTL](/runs#time-to-live-ttl) on a scheduled task to automatically expire runs that aren't dequeued in time. This is useful when a schedule fires while the previous run is still executing - rather than queueing up stale runs, they'll expire:
71
71
 
@@ -142,7 +142,7 @@ To use imperative schedules you need to do two things:
142
142
 
143
143
  ## Spreading runs with windows
144
144
 
145
- By default a schedule runs at its exact cron time. When many schedules share the same cron pattern, such as a daily `0 9 * * *`, they all fire at the same time and load your downstream systems as well as ours. A **window** spreads those runs out: each schedule is assigned a stable time within the window after its cron time, so the load is smoothed while each schedule keeps firing at a predictable, repeatable moment.
145
+ When many schedules share the same cron pattern, such as a daily `0 9 * * *`, they all fire at the same time and load your downstream systems as well as ours. A **window** spreads those runs out: each schedule is assigned a stable time within the window after its cron time, so the load is smoothed while each schedule keeps firing at a predictable, repeatable moment. New schedules use a [60-minute window by default](#default-window).
146
146
 
147
147
  The assigned time is deterministic. A given schedule always lands at the same offset for a given interval, so runs don't jump around between occurrences. And you can always see exactly when the next run will start.
148
148
 
@@ -151,7 +151,7 @@ Set a `window` as either:
151
151
  - **An absolute duration** in whole minutes or hours, up to 24 hours: `"30m"`, `"2h"`, `"24h"`. Absolute windows are capped at the next cron time, so a run is never delayed past its following occurrence.
152
152
  - **A percentage** of the interval between runs: `"30%"`, `"100%"`. A `"50%"` window on an hourly schedule spreads runs across the first 30 minutes of each hour.
153
153
 
154
- Set `"0m"` (or `"0%"`) for no spreading — the run fires at its exact cron time.
154
+ Set `"0m"` (or `"0%"`) for no spreading — the run fires at its exact cron time. This also overrides the [default window](#default-window).
155
155
 
156
156
  Declarative schedules set the window on the `cron` object:
157
157
 
@@ -184,6 +184,12 @@ const createdSchedule = await schedules.create({
184
184
  is the assigned time the run will actually start.
185
185
  </Note>
186
186
 
187
+ ### Default window
188
+
189
+ New schedules get a default 60-minute window unless you set one, so each run starts at a stable time within the hour after its cron time. Like any absolute window, it's capped at the next cron time: a schedule that runs every 15 minutes is spread across those 15 minutes.
190
+
191
+ To opt a new schedule out of the default window, set `window: "0m"`.
192
+
187
193
  ### Free-plan minimum window
188
194
 
189
195
  Schedules created while an organization is on a free plan run no more than once per hour and use a minimum 60-minute window.
@@ -746,6 +746,12 @@ await myTask.batchTrigger([{ payload: { some: "data" }, options: { delay: "1h" }
746
746
  manually, convert Dates to ISO strings first (e.g., `date.toISOString()`).
747
747
  </Note>
748
748
 
749
+ <Tip>
750
+ A `delay` doesn't merge triggers: each trigger still creates its own run. To collapse a burst of
751
+ triggers into one run, use [`debounce`](#debounce). See [runs.list() best
752
+ practices](/runs/list-best-practices#using-a-fixed-delay-to-space-out-triggers).
753
+ </Tip>
754
+
749
755
  ### `ttl`
750
756
 
751
757
  You can set a TTL (time to live) when triggering a task, which will automatically expire the run if it hasn't started within the specified time. This is useful for ensuring that a run doesn't get stuck in the queue for too long.
@@ -814,6 +820,8 @@ export const myTask = task({
814
820
  });
815
821
  ```
816
822
 
823
+ Build the key from the work being done, like an order or event ID. A random value on every trigger never matches, so it never dedupes. See [runs.list() best practices](/runs/list-best-practices#using-a-random-idempotency-key).
824
+
817
825
  For more information, see our [Idempotency](/idempotency) documentation.
818
826
 
819
827
  ### `idempotencyKeyTTL`
@@ -243,13 +243,17 @@ This webhook is sent when a run fails. The payload is available on the `object`
243
243
  </ParamField>
244
244
 
245
245
  <ParamField path="object.environment.type" type="string">
246
- Environment type (STAGING or PRODUCTION)
246
+ Environment type (`PRODUCTION`, `STAGING`, or `PREVIEW`)
247
247
  </ParamField>
248
248
 
249
249
  <ParamField path="object.environment.slug" type="string">
250
250
  Environment slug
251
251
  </ParamField>
252
252
 
253
+ <ParamField path="object.environment.branchName" type="string">
254
+ Environment branch name (optional)
255
+ </ParamField>
256
+
253
257
  <ParamField path="object.organization.id" type="string">
254
258
  Organization ID
255
259
  </ParamField>
@@ -311,7 +315,7 @@ This webhook is sent when a deployment succeeds. The payload is available on the
311
315
  </ParamField>
312
316
 
313
317
  <ParamField path="object.environment.type" type="string">
314
- Environment type (STAGING or PRODUCTION)
318
+ Environment type (`PRODUCTION`, `STAGING`, or `PREVIEW`)
315
319
  </ParamField>
316
320
 
317
321
  <ParamField path="object.environment.slug" type="string">
@@ -346,6 +350,54 @@ This webhook is sent when a deployment succeeds. The payload is available on the
346
350
  Project name
347
351
  </ParamField>
348
352
 
353
+ <ParamField path="object.git" type="object">
354
+ Git metadata for the deployment (optional). When present, `branch`, `commitSha`, `commitMessage`, `commitUrl` and `branchUrl` are always set.
355
+ </ParamField>
356
+
357
+ <ParamField path="object.git.branch" type="string">
358
+ Git branch
359
+ </ParamField>
360
+
361
+ <ParamField path="object.git.commitSha" type="string">
362
+ Short commit SHA (first 7 characters)
363
+ </ParamField>
364
+
365
+ <ParamField path="object.git.commitMessage" type="string">
366
+ Commit message (empty string if unavailable)
367
+ </ParamField>
368
+
369
+ <ParamField path="object.git.commitUrl" type="string">
370
+ URL of the commit
371
+ </ParamField>
372
+
373
+ <ParamField path="object.git.branchUrl" type="string">
374
+ URL of the branch
375
+ </ParamField>
376
+
377
+ <ParamField path="object.git.pullRequestNumber" type="number">
378
+ Pull request number (optional)
379
+ </ParamField>
380
+
381
+ <ParamField path="object.git.pullRequestTitle" type="string">
382
+ Pull request title (optional)
383
+ </ParamField>
384
+
385
+ <ParamField path="object.git.pullRequestUrl" type="string">
386
+ Pull request URL (optional)
387
+ </ParamField>
388
+
389
+ <ParamField path="object.git.provider" type="string">
390
+ Git provider (optional)
391
+ </ParamField>
392
+
393
+ <ParamField path="object.vercel" type="object">
394
+ Vercel metadata for the deployment (optional)
395
+ </ParamField>
396
+
397
+ <ParamField path="object.vercel.deploymentUrl" type="string">
398
+ URL of the deployment in the Vercel dashboard
399
+ </ParamField>
400
+
349
401
  ### Deployment Failed Alert
350
402
 
351
403
  This webhook is sent when a deployment fails. The payload is available on the `object` property:
@@ -391,7 +443,7 @@ This webhook is sent when a deployment fails. The payload is available on the `o
391
443
  </ParamField>
392
444
 
393
445
  <ParamField path="object.environment.type" type="string">
394
- Environment type (STAGING or PRODUCTION)
446
+ Environment type (`PRODUCTION`, `STAGING`, or `PREVIEW`)
395
447
  </ParamField>
396
448
 
397
449
  <ParamField path="object.environment.slug" type="string">
@@ -426,6 +478,53 @@ This webhook is sent when a deployment fails. The payload is available on the `o
426
478
  Project name
427
479
  </ParamField>
428
480
 
481
+ <ParamField path="object.git" type="object">
482
+ Git metadata for the deployment (optional). When present, `branch`, `commitSha`, `commitMessage`, `commitUrl` and `branchUrl` are always set.
483
+ </ParamField>
484
+
485
+ <ParamField path="object.git.branch" type="string">
486
+ Git branch
487
+ </ParamField>
488
+
489
+ <ParamField path="object.git.commitSha" type="string">
490
+ Short commit SHA (first 7 characters)
491
+ </ParamField>
492
+
493
+ <ParamField path="object.git.commitMessage" type="string">
494
+ Commit message (empty string if unavailable)
495
+ </ParamField>
496
+
497
+ <ParamField path="object.git.commitUrl" type="string">
498
+ URL of the commit
499
+ </ParamField>
500
+
501
+ <ParamField path="object.git.branchUrl" type="string">
502
+ URL of the branch
503
+ </ParamField>
504
+
505
+ <ParamField path="object.git.pullRequestNumber" type="number">
506
+ Pull request number (optional)
507
+ </ParamField>
508
+
509
+ <ParamField path="object.git.pullRequestTitle" type="string">
510
+ Pull request title (optional)
511
+ </ParamField>
512
+
513
+ <ParamField path="object.git.pullRequestUrl" type="string">
514
+ Pull request URL (optional)
515
+ </ParamField>
516
+
517
+ <ParamField path="object.git.provider" type="string">
518
+ Git provider (optional)
519
+ </ParamField>
520
+
521
+ <ParamField path="object.vercel" type="object">
522
+ Vercel metadata for the deployment (optional)
523
+ </ParamField>
524
+
525
+ <ParamField path="object.vercel.deploymentUrl" type="string">
526
+ URL of the deployment in the Vercel dashboard
527
+ </ParamField>
429
528
 
430
529
  ### Error Group Alert
431
530
 
@@ -251,7 +251,7 @@ If runs are staying in the `QUEUED` state for extended periods, check your concu
251
251
  **Solutions:**
252
252
 
253
253
  - **Increase concurrency limits** - If you're on a paid plan, increase your environment concurrency limit via the dashboard
254
- - **Review queue concurrency limits** - Check if individual queues have restrictive `concurrencyLimit` settings
254
+ - **Review concurrency settings** - Check if your tasks have restrictive `concurrency` settings, or your queues a restrictive legacy `concurrencyLimit`. See [Concurrency](/concurrency)
255
255
  - **Check for stuck runs** - See if stalled runs are blocking new executions
256
256
 
257
257
  ### Runs queued because a billing limit was reached
@@ -39,12 +39,12 @@ You can deploy using [GitHub Actions](/github-actions). We recommend that you lo
39
39
 
40
40
  <Steps>
41
41
  <Step title="Find your workflow file">
42
- In your `.githubs/workflows` folder you can find your workflow yml files. You may have a prod
42
+ In your `.github/workflows` folder you can find your workflow yml files. You may have a prod
43
43
  and staging one.
44
44
  </Step>
45
45
  <Step title="Update the version for the run command">
46
46
  In the steps you'll see a `run` command. It will run the trigger.dev deploy CLI command. Make
47
- sure to update this version to the latest version (e.g. `npx trigger.dev@3.0.0 deploy`).
47
+ sure to update this version to the latest version (e.g. `npx trigger.dev@4.7.3 deploy`).
48
48
  </Step>
49
49
  </Steps>
50
50
 
@@ -57,7 +57,7 @@ For example:
57
57
  ```json
58
58
  {
59
59
  "devDependencies": {
60
- "trigger.dev": "3.0.0"
60
+ "trigger.dev": "4.7.3"
61
61
  }
62
62
  }
63
63
  ```
@@ -129,7 +129,17 @@ The integration sets this up for you:
129
129
  - It passes your commit SHA as the deployment's external id.
130
130
  - `VERCEL_GIT_COMMIT_SHA` is available at runtime on Vercel, so the SDK finds the matching id with no work from you.
131
131
 
132
- There is nothing to enable, and it works in production, staging and preview alike. Nothing is gated: your Vercel deployment is never held back.
132
+ There is nothing to enable, and it works in production, staging and preview alike.
133
+
134
+ ### Production waits for your tasks
135
+
136
+ A production deployment from a push to the branch Trigger.dev builds for production gets a blocking **Trigger.dev Build** check. Vercel holds the deployment until the task build for the same commit finishes, then assigns your production domains as usual. If the task build fails or is canceled, the check fails and the Vercel deployment doesn't go live, so your app never ships against tasks that didn't build.
137
+
138
+ If no task build for the commit has started within 10 minutes, the check fails too.
139
+
140
+ To retry after a failed build, re-run the **Trigger.dev deployment** check on the commit in GitHub. Once that build succeeds, redeploy from Vercel or push a new commit.
141
+
142
+ A production deployment with no task build to wait for is never held: its check is marked skipped. That covers deployments from the Vercel CLI, from a branch that isn't your production branch in Trigger.dev, and from projects without a connected GitHub repository. Preview and staging deployments aren't held either: their check passes straight away, and version skew protection makes their runs wait for the matching task build instead.
133
143
 
134
144
  <Note>
135
145
  Version skew protection requires the `@trigger.dev/sdk` release that introduces external
@@ -146,8 +156,8 @@ To opt out, set `TRIGGER_AUTOMATIC_SKEW_VERSION_PROTECTION` to `0` on your Verce
146
156
  <Warning>
147
157
  **Automatic atomic deployments are deprecated.** Use [version skew
148
158
  protection](/deployment/atomic-deployment) instead — it needs no second Vercel deployment,
149
- never gates your app's deploy, doesn't touch `Auto-assign Custom Production Domains`, and covers
150
- staging and preview as well as production.
159
+ doesn't touch `Auto-assign Custom Production Domains`, and covers staging and preview as well as
160
+ production. Production deployments still wait for your task build through the blocking check.
151
161
 
152
162
  Nothing is being switched off. The setting stays in your project's Vercel settings, keeps working,
153
163
  and remains the way to hold your Vercel deployment back until your tasks have built. New
@@ -51,7 +51,7 @@ Because your application and your tasks deploy separately, a release of your app
51
51
 
52
52
  ## Retries and reattempts
53
53
 
54
- When a task has an uncaught error it will [retry](/errors-retrying), assuming you have not set `maxAttempts` to 0. Retries are locked to the original version of the run.
54
+ When a task has an uncaught error it will [retry](/errors-retrying), if the task has retries configured (the `retry` option or `retries.default` in your config). Retries are locked to the original version of the run.
55
55
 
56
56
  ## Replays
57
57
 
@@ -135,7 +135,7 @@ Create a waitpoint token.
135
135
  The `createToken` function accepts an object with the following properties:
136
136
 
137
137
  <ParamField query="timeout" type="string" optional>
138
- The maximum amount of time to wait for the token to be completed. Defaults to "10m".
138
+ The maximum amount of time to wait for the token to be completed. If omitted, the token has no timeout. Accepts a duration string like `"30s"` or `"4w"`, or a `Date`.
139
139
  </ParamField>
140
140
 
141
141
  <ParamField query="idempotencyKey" type="string" optional>
@@ -144,7 +144,7 @@ The `createToken` function accepts an object with the following properties:
144
144
  </ParamField>
145
145
 
146
146
  <ParamField query="idempotencyKeyTTL" type="string" optional>
147
- The time to live for the idempotency key. Defaults to "1h".
147
+ The time to live for the idempotency key. If omitted, the key does not expire.
148
148
  </ParamField>
149
149
 
150
150
  <ParamField query="tags" type="string[]" optional>
@@ -380,7 +380,7 @@ The `listTokens` function accepts an object with the following properties:
380
380
  </ParamField>
381
381
 
382
382
  <ParamField query="period" type="string" optional>
383
- The period to filter by. Can be one of: `1h`, `1d`, `7d`, `30d`.
383
+ The period to filter by, as a duration string such as `1h`, `1d`, `7d` or `30d`.
384
384
  </ParamField>
385
385
 
386
386
  <ParamField query="from" type="Date | number" optional>
@@ -0,0 +1,128 @@
1
+ ---
2
+ title: "Channels (chat frontends)"
3
+ description: "Point Slack (or any chat surface) at an agent: messages become turns and replies post back."
4
+ sidebarTitle: "Channels"
5
+ ---
6
+
7
+ A [session route](/webhooks/session-routing) delivers a verified event to an agent as an [action](/ai-chat/actions): the agent reacts, and the response is a side effect. A **channel** is the other half: the webhook IS the chat surface. Inbound messages become **turns** (the normal `run()` loop), and the agent's reply is posted **back** to the surface. A Slack thread becomes a real conversation with the agent, exactly like the browser chat, just a different frontend.
8
+
9
+ List channels on a [`chat.agent`](/ai-chat/overview) alongside (or instead of) `events`:
10
+
11
+ ```ts
12
+ import { webhooks } from "@trigger.dev/sdk";
13
+ import { chat } from "@trigger.dev/sdk/ai";
14
+ import { slack, webhookSource } from "@trigger.dev/slack";
15
+ import { anthropic } from "@ai-sdk/anthropic";
16
+
17
+ const slackEvents = webhooks.endpoint.define({ id: "slack", source: webhookSource() });
18
+
19
+ export const supportAgent = chat.agent({
20
+ id: "support-agent",
21
+ channels: [slack({ id: "support-slack", endpoint: slackEvents, token: process.env.SLACK_BOT_TOKEN! })],
22
+ run: async ({ messages, streamText }) =>
23
+ streamText({ model: anthropic("claude-sonnet-4-5"), messages }),
24
+ });
25
+ ```
26
+
27
+ The `run()` loop is unchanged: the agent does not know or care that it is talking to Slack. One verified Slack message in a thread is routed to a durable [session](/ai-chat/sessions) keyed to that thread, run as a turn, and the reply is posted into the thread.
28
+
29
+ ## Slack
30
+
31
+ `slack()` from `@trigger.dev/slack` makes the agent a native Slack agent: the session shows its status, the reply streams into the thread with tool calls as task cards, approvals are buttons and Slack's stop button stops the turn. Shared images and PDFs reach the model, messages sent mid-reply can be folded into it, and the agent can post its own buttons. See [Slack agents](/webhooks/slack) for setup and options.
32
+
33
+ ## Linear
34
+
35
+ `linear()` from `@trigger.dev/linear` makes the agent a Linear agent: assign it an issue or @mention it and it works the issue inside Linear's agent session UI, with thoughts, actions, a plan and approvals. See [Linear agents](/webhooks/linear).
36
+
37
+ ## Approvals and interactive controls
38
+
39
+ An agent on a channel can pause a turn to get a human decision, approving a refund or confirming a deletion, and resume once someone clicks a button in the thread. `slack()` renders Approve and Deny buttons for you and collapses them to the decision once clicked; you can replace them, or give a tool its own UI whose click becomes the tool's result (see [Slack agents](/webhooks/slack#approvals-and-buttons)). See [human-in-the-loop](/webhooks/human-in-the-loop).
40
+
41
+ ## Any surface: `chat.channels.custom`
42
+
43
+ For a surface without a preset, `chat.channels.custom` is the generic connector. You supply the [endpoint](/webhooks/overview) it subscribes to, the session `key`, the `inbound` map, and the egress `send`:
44
+
45
+ ```ts
46
+ import { chat } from "@trigger.dev/sdk/ai";
47
+ import { webhooks } from "@trigger.dev/sdk";
48
+
49
+ const surface = webhooks.endpoint.define({
50
+ id: "my-surface",
51
+ source: webhooks.custom<MyEvent>({ /* verifier config */ }),
52
+ });
53
+
54
+ const mySurface = chat.channels.custom({
55
+ id: "my-surface",
56
+ endpoint: surface,
57
+ key: "{event.conversationId}",
58
+ inbound: (e) => e.text,
59
+ outbound: (reply) => (reply.text ? { text: reply.text } : null), // null posts nothing
60
+ send: async (message, ctx) => {
61
+ const ref = await postToMySurface(ctx.event, message.text, ctx.previousRef);
62
+ return { ref }; // an existing ref means edit-in-place on the next turn
63
+ },
64
+ });
65
+ ```
66
+
67
+ `send` is called to post the reply. `ctx.previousRef` is the ref you returned last time, so streaming or a follow-up edits the same message instead of posting a new one. Return `null` from `outbound` to stay silent (a tool-only turn, say).
68
+
69
+ An event that `inbound` maps to an empty message (no text, and no file or data parts) runs no turn. A click or system event your surface sends to the same endpoint is dropped rather than answered with an empty prompt.
70
+
71
+ List the connector in `chat.agent({ channels: [mySurface] })`, which subscribes the agent to the endpoint with the id `agentId:connectorId`. Other subscribers on the endpoint keep receiving its deliveries.
72
+
73
+ ## Stopping a turn from the channel
74
+
75
+ A connector can map an event to a control with `onControl`. Returning `{ kind: "stop" }` while a turn is running aborts it through the run's `stopSignal`, exactly like the browser's stop button: the partial reply posts with `stopped: true` and the event is consumed instead of running as a turn. `linear()` and `slack()` map their stop buttons this way; a custom connector might map a `/stop` command:
76
+
77
+ ```ts
78
+ chat.channels.custom({
79
+ // ...
80
+ onControl: (e) => (e.text.trim() === "/stop" ? { kind: "stop" } : null),
81
+ });
82
+ ```
83
+
84
+ When nothing is running, the event is handled by `inbound` as an ordinary message. Return `whenIdle: "drop"` to consume it instead: no turn runs, and if the session is waiting on an [approval](/webhooks/human-in-the-loop) the pending tool calls are resolved as stopped and the connector's `cancelInteraction(cancelled, ctx)` takes the controls down (each cancelled call carries the `ref` its controls were posted with). Set `at` to when the user asked (epoch milliseconds), and a stop older than the latest turn is consumed without stopping it or cancelling that turn's approval, so a late or repeated delivery never stops the wrong turn. A stop pressed while the latest turn ran but delivered after it ended is consumed too, rather than handled as a message.
85
+
86
+ ## Status and native streaming
87
+
88
+ Two more hooks let a connector mirror the session into a surface that has its own notion of an agent, the way `slack()` does:
89
+
90
+ - `lifecycle(state, ctx)` reports `working` when a turn starts (and every 50 minutes while it runs), `waiting` when it pauses on a human decision, `idle` when it ends for any reason, and `closed` on `chat.close()`. `ctx.firstTurn` and `ctx.prompt` are there to title a new conversation. Calls are serialized, so an older report never lands after a newer one.
91
+ - `stream(ctx)`, with `delivery: "stream"`, returns a per-turn transport `{ text(delta), progress?(event), end(outcome) }`. The runtime feeds it the reply's markdown deltas and tool progress, then calls `end({ stopped, pending, errorText? })` once. Resolve `{ delivered: true }` when the reply reached the surface, and the runtime does not post it again. Call `ctx.opened(ref)` once the surface has created the stream message: the ref is saved with the session while the turn runs, and if the worker dies mid-stream the next one calls the connector's `endInterruptedStream(ref, ctx)` before it answers, so the old stream does not look like it is still being written.
92
+
93
+ ## Bound sessions
94
+
95
+ The first channel message binds the session to its connector and thread. From then on, any turn on that session posts its answer back through the same connector, including turns no channel delivery started: a [`chat.event`](/webhooks/session-routing#answering-into-a-channel) action that returns `chat.turn()`, or a message from a browser client sharing the session via [`chat.createWatchToken`](/ai-chat/reference). `slack()` binds to the team, channel, thread and the user it is answering; a custom connector opts in with `bind`:
96
+
97
+ ```ts
98
+ chat.channels.custom({
99
+ id: "my-surface",
100
+ endpoint: surface,
101
+ key: "{event.conversationId}",
102
+ inbound: (e) => ({ message: e.text, externalUserId: e.userId, externalMessageId: e.id }),
103
+ bind: (e) => ({ conversationId: e.conversationId }),
104
+ send: async (message, ctx) => {
105
+ // ctx.event is absent on a bound turn; ctx.handle always carries what bind() returned
106
+ const target = ctx.handle ?? { conversationId: ctx.event!.conversationId };
107
+ return { ref: await postToMySurface(target, message.text, ctx.previousRef) };
108
+ },
109
+ });
110
+ ```
111
+
112
+ `inbound` may return `{ message, externalUserId, externalMessageId }` as well as a plain string or `UIMessage`. The runtime stamps every channel-delivered user message with `metadata: { source: "channel", connectorId, provider, deliveryId, externalDeliveryId?, externalUserId?, externalMessageId? }` (`deliveryId` is the delivery's `whd_` id and `externalDeliveryId` the provider's), and a [transcript storage](/ai-chat/transcript-storage) receives `source: "channel"` and the `connectorId` on its save context, so a store you own can attribute and reconcile channel messages.
113
+
114
+ ## Files, buttons and steering
115
+
116
+ Three more hooks cover what a chat surface carries besides text:
117
+
118
+ - `download(url, { handle })` fetches files only the surface's credentials can read. Have `inbound` return file parts on the surface's URLs, and the `streamText` passed to `run()` asks each connector's `download` for every file URL in the prompt (return null for URLs it doesn't own). Downloads are cached for the run and the transcript keeps the URL. A file `download` throws on becomes a short note in that turn's messages, and the turn still runs.
119
+ - `toAction(event)` maps a click on a control the agent posted to an action. It reaches `onAction`, validated against `actionSchema`, with the click itself as `channel.event`. Returning `chat.turn(message)` answers in the bound thread. Return null for anything else, such as a click answering a pending tool call, which `onInteraction` handles.
120
+ - With [`pendingMessages`](/ai-chat/pending-messages) on the agent, a channel message that arrives while a turn is running goes through `inbound` and is offered to `shouldInject` like a browser message (its `metadata.source` is `"channel"`). Stops, clicks and empty messages are never offered.
121
+ - `received(ctx)` acknowledges a channel message that arrives while a turn is running, as soon as it arrives and whether or not `pendingMessages` folds it in, so its wording should be true either way: `slack()` reacts with 👀, `linear()` posts a thought. Stops, clicks and empty messages are not acknowledged.
122
+
123
+ ## Channels vs events
124
+
125
+ Both are inbound surfaces on a `chat.agent`, and an agent can list both:
126
+
127
+ - [`events`](/webhooks/session-routing) (`chat.event`): the webhook is a signal. Delivered to `onAction`; the agent acts, no reply is sent back.
128
+ - `channels` (`slack`, `chat.channels.custom`): the webhook is a chat frontend. Delivered as a turn to `run()`; the reply is posted back.
@@ -0,0 +1,80 @@
1
+ ---
2
+ title: "Connect a webhook provider"
3
+ description: "Register an endpoint's URL and signing secret with the provider, by hand or with an AI coding agent, then check that events arrive."
4
+ sidebarTitle: "Connect a provider"
5
+ ---
6
+
7
+ Connect a provider to an [endpoint](/webhooks/overview) by registering the endpoint's URL with the provider and giving Trigger.dev the credential that verifies its requests.
8
+
9
+ Deploy the project or start `trigger dev` first. Once the CLI finds the endpoint, it appears on the environment's **Webhooks** page. The **Signing secret** column shows **Ready** when the endpoint can verify deliveries and **Needs secret** until then. Every delivery is rejected while an endpoint needs its secret.
10
+
11
+ ## Set it up with an AI agent
12
+
13
+ Each endpoint has a setup prompt: the URL, the events its subscribers filter on, the provider's own setup steps, and how to generate or store the signing secret. Choose **Copy setup prompt** on the Webhooks page or the endpoint's page, and paste it into an AI coding agent such as Claude Code.
14
+
15
+ The prompt tells the agent to use the Trigger.dev MCP server (`npx trigger.dev@latest install-mcp`), which has these tools:
16
+
17
+ | Tool | What it does |
18
+ | --- | --- |
19
+ | `list_webhook_endpoints` | Lists the environment's endpoints, with their URL and whether a secret is set. |
20
+ | `get_webhook_endpoint_details` | Reads one endpoint: URL, subscribers and their filters, secret state, and the setup prompt. |
21
+ | `generate_webhook_secret` | Generates a signing secret and returns it once, for providers where you choose the secret. |
22
+ | `set_webhook_secret` | Stores a secret the provider issued. |
23
+
24
+ Without the MCP server, the same operations are available on the REST API with the environment's secret key:
25
+
26
+ | Request | What it does |
27
+ | --- | --- |
28
+ | [`GET /api/v1/webhooks/endpoints`](/management/webhooks/list-endpoints) | Lists the environment's endpoints, with their URL and whether a secret is set. |
29
+ | [`GET /api/v1/webhooks/endpoints/{endpointId}`](/management/webhooks/retrieve-endpoint) | Reads the endpoint, including `subscribers` and `setupPrompt`. |
30
+ | [`POST /api/v1/webhooks/endpoints/{endpointId}/rotate-secret`](/management/webhooks/generate-secret) | Generates a secret and returns it once. |
31
+ | [`PUT /api/v1/webhooks/endpoints/{endpointId}/secret`](/management/webhooks/set-secret) | Stores a secret, sent as `{ "secret": "..." }`. |
32
+
33
+ `endpointId` is the endpoint's declared id (the `id` passed to `webhooks.endpoint.define`) or its `wh_` id, shown on its page and returned by `list_webhook_endpoints`:
34
+
35
+ ```bash
36
+ curl -X PUT "https://api.trigger.dev/api/v1/webhooks/endpoints/stripe/secret" \
37
+ -H "Authorization: Bearer $TRIGGER_SECRET_KEY" \
38
+ -H "Content-Type: application/json" \
39
+ -d '{"secret": "whsec_..."}'
40
+ ```
41
+
42
+ ## Set it up by hand
43
+
44
+ <Steps>
45
+ <Step title="Copy the webhook URL">
46
+ Open the endpoint from the Webhooks page and copy the **Webhook URL**.
47
+ </Step>
48
+ <Step title="Configure the provider">
49
+ Add the URL as the webhook destination in your provider's dashboard and select the events your subscribers need. The provider's setup determines where the verification credential comes from:
50
+
51
+ | Provider setup | Action |
52
+ | --- | --- |
53
+ | The provider supplies a signing secret, as Stripe and Svix do | Copy the secret from the provider, then choose **Set signing secret** on the endpoint. |
54
+ | The provider accepts a secret you choose, as GitHub does | Choose **Generate secret** on the endpoint, then paste the value into the provider's webhook configuration. |
55
+ | The provider uses an asymmetric signature, as Discord does | Copy the application's public key from the provider and choose **Set public key** on the endpoint. |
56
+
57
+ Set the credential before sending a test event or asking the provider to validate the URL.
58
+ </Step>
59
+ </Steps>
60
+
61
+ ## Verify the connection
62
+
63
+ Send a test event from your provider, or use the endpoint's **Test** tab. The event appears on the endpoint's **Deliveries** tab. Open it to see which subscribers it reached and follow the links to their runs; see [Deliveries and endpoints](/webhooks/deliveries) for the statuses.
64
+
65
+ If no delivery appears, check that the provider uses the copied URL and that both sides have the same signing secret or public key.
66
+
67
+ ## Verify a WhatsApp endpoint URL
68
+
69
+ `webhooks.whatsapp()` also needs a verify token for Meta's URL check. On the endpoint, choose **Generate verify token** and paste the value into Meta's **Verify Token** field. Set Meta's App Secret as the signing secret, then validate the URL in Meta's dashboard.
70
+
71
+ The verify token answers the URL check. The signing secret verifies event deliveries. See [Sources and verification](/webhooks/sources#endpoint-validation) for the response behavior.
72
+
73
+ ## Rotate a credential
74
+
75
+ Choose **Rotate signing secret** to replace a provider-supplied secret. For a secret you generate, choose **Regenerate secret**, copy the new value, and update the provider's configuration. Use **Rotate public key** when replacing an asymmetric provider's key.
76
+
77
+ <Warning>
78
+ Trigger.dev stores credentials encrypted and doesn't display saved values. Copy a generated secret
79
+ before closing the dialog. Deliveries signed with the old secret fail verification after rotation.
80
+ </Warning>