@workflow/core 5.0.0-beta.5 → 5.0.0-beta.50

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 (310) hide show
  1. package/dist/attribute-changes.d.ts +5 -0
  2. package/dist/attribute-changes.d.ts.map +1 -0
  3. package/dist/attribute-changes.js +25 -0
  4. package/dist/capabilities.d.ts +35 -2
  5. package/dist/capabilities.d.ts.map +1 -1
  6. package/dist/capabilities.js +74 -5
  7. package/dist/capture-stack.d.ts +1 -1
  8. package/dist/capture-stack.js +2 -2
  9. package/dist/class-serialization.d.ts +32 -0
  10. package/dist/class-serialization.d.ts.map +1 -1
  11. package/dist/class-serialization.js +37 -1
  12. package/dist/classify-error.d.ts +19 -3
  13. package/dist/classify-error.d.ts.map +1 -1
  14. package/dist/classify-error.js +98 -6
  15. package/dist/context-violation-error.d.ts +5 -5
  16. package/dist/context-violation-error.d.ts.map +1 -1
  17. package/dist/context-violation-error.js +8 -6
  18. package/dist/create-hook.d.ts +72 -7
  19. package/dist/create-hook.d.ts.map +1 -1
  20. package/dist/create-hook.js +1 -1
  21. package/dist/define-hook.js +2 -2
  22. package/dist/describe-error.d.ts +7 -7
  23. package/dist/describe-error.d.ts.map +1 -1
  24. package/dist/describe-error.js +97 -12
  25. package/dist/encryption.d.ts +38 -4
  26. package/dist/encryption.d.ts.map +1 -1
  27. package/dist/encryption.js +96 -14
  28. package/dist/events-consumer.d.ts +271 -0
  29. package/dist/events-consumer.d.ts.map +1 -1
  30. package/dist/events-consumer.js +646 -42
  31. package/dist/flushable-stream.d.ts +56 -1
  32. package/dist/flushable-stream.d.ts.map +1 -1
  33. package/dist/flushable-stream.js +123 -11
  34. package/dist/global.d.ts +36 -2
  35. package/dist/global.d.ts.map +1 -1
  36. package/dist/global.js +21 -6
  37. package/dist/index.d.ts +2 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +3 -2
  40. package/dist/log-format.d.ts +2 -2
  41. package/dist/log-format.js +7 -7
  42. package/dist/logger.d.ts +1 -0
  43. package/dist/logger.d.ts.map +1 -1
  44. package/dist/logger.js +10 -6
  45. package/dist/private.d.ts +226 -9
  46. package/dist/private.d.ts.map +1 -1
  47. package/dist/private.js +626 -7
  48. package/dist/replay-payload-cache.d.ts +64 -0
  49. package/dist/replay-payload-cache.d.ts.map +1 -0
  50. package/dist/replay-payload-cache.js +158 -0
  51. package/dist/runtime/compute-instance.d.ts +13 -0
  52. package/dist/runtime/compute-instance.d.ts.map +1 -0
  53. package/dist/runtime/compute-instance.js +14 -0
  54. package/dist/runtime/constants.d.ts +315 -0
  55. package/dist/runtime/constants.d.ts.map +1 -1
  56. package/dist/runtime/constants.js +502 -16
  57. package/dist/runtime/count-step-started-events.d.ts +52 -0
  58. package/dist/runtime/count-step-started-events.d.ts.map +1 -0
  59. package/dist/runtime/count-step-started-events.js +72 -0
  60. package/dist/runtime/deployment-guard.d.ts +99 -0
  61. package/dist/runtime/deployment-guard.d.ts.map +1 -0
  62. package/dist/runtime/deployment-guard.js +154 -0
  63. package/dist/runtime/get-port-lazy.d.ts +15 -0
  64. package/dist/runtime/get-port-lazy.d.ts.map +1 -1
  65. package/dist/runtime/get-port-lazy.js +75 -6
  66. package/dist/runtime/get-world-lazy.d.ts +5 -14
  67. package/dist/runtime/get-world-lazy.d.ts.map +1 -1
  68. package/dist/runtime/get-world-lazy.js +19 -29
  69. package/dist/runtime/helpers.d.ts +341 -29
  70. package/dist/runtime/helpers.d.ts.map +1 -1
  71. package/dist/runtime/helpers.js +559 -57
  72. package/dist/runtime/quickjs-assets.generated.d.ts +14 -0
  73. package/dist/runtime/quickjs-assets.generated.d.ts.map +1 -0
  74. package/dist/runtime/quickjs-assets.generated.js +30 -0
  75. package/dist/runtime/quickjs-entrypoint.d.ts +136 -0
  76. package/dist/runtime/quickjs-entrypoint.d.ts.map +1 -0
  77. package/dist/runtime/quickjs-entrypoint.js +1686 -0
  78. package/dist/runtime/quickjs-runtime.d.ts +244 -0
  79. package/dist/runtime/quickjs-runtime.d.ts.map +1 -0
  80. package/dist/runtime/quickjs-runtime.js +2427 -0
  81. package/dist/runtime/quickjs-serde.d.ts +107 -0
  82. package/dist/runtime/quickjs-serde.d.ts.map +1 -0
  83. package/dist/runtime/quickjs-serde.js +2159 -0
  84. package/dist/runtime/replay-budget.d.ts +97 -0
  85. package/dist/runtime/replay-budget.d.ts.map +1 -0
  86. package/dist/runtime/replay-budget.js +139 -0
  87. package/dist/runtime/replay-recovery-reporter.d.ts +36 -0
  88. package/dist/runtime/replay-recovery-reporter.d.ts.map +1 -0
  89. package/dist/runtime/replay-recovery-reporter.js +64 -0
  90. package/dist/runtime/resume-hook.d.ts +91 -8
  91. package/dist/runtime/resume-hook.d.ts.map +1 -1
  92. package/dist/runtime/resume-hook.js +512 -89
  93. package/dist/runtime/resume-latency.d.ts +150 -0
  94. package/dist/runtime/resume-latency.d.ts.map +1 -0
  95. package/dist/runtime/resume-latency.js +155 -0
  96. package/dist/runtime/run-id-time.d.ts +19 -0
  97. package/dist/runtime/run-id-time.d.ts.map +1 -0
  98. package/dist/runtime/run-id-time.js +42 -0
  99. package/dist/runtime/run.d.ts +72 -7
  100. package/dist/runtime/run.d.ts.map +1 -1
  101. package/dist/runtime/run.js +280 -53
  102. package/dist/runtime/runs.d.ts +54 -3
  103. package/dist/runtime/runs.d.ts.map +1 -1
  104. package/dist/runtime/runs.js +126 -16
  105. package/dist/runtime/start.d.ts +101 -2
  106. package/dist/runtime/start.d.ts.map +1 -1
  107. package/dist/runtime/start.js +352 -61
  108. package/dist/runtime/step-executor.d.ts +205 -5
  109. package/dist/runtime/step-executor.d.ts.map +1 -1
  110. package/dist/runtime/step-executor.js +760 -127
  111. package/dist/runtime/step-latency.d.ts +220 -0
  112. package/dist/runtime/step-latency.d.ts.map +1 -0
  113. package/dist/runtime/step-latency.js +215 -0
  114. package/dist/runtime/step-ownership.d.ts +72 -0
  115. package/dist/runtime/step-ownership.d.ts.map +1 -0
  116. package/dist/runtime/step-ownership.js +114 -0
  117. package/dist/runtime/step-single-flight.d.ts +12 -0
  118. package/dist/runtime/step-single-flight.d.ts.map +1 -0
  119. package/dist/runtime/step-single-flight.js +75 -0
  120. package/dist/runtime/suspension-handler.d.ts +269 -7
  121. package/dist/runtime/suspension-handler.d.ts.map +1 -1
  122. package/dist/runtime/suspension-handler.js +1253 -125
  123. package/dist/runtime/unserializable-step.d.ts +34 -0
  124. package/dist/runtime/unserializable-step.d.ts.map +1 -0
  125. package/dist/runtime/unserializable-step.js +52 -0
  126. package/dist/runtime/vm-mode.d.ts +44 -0
  127. package/dist/runtime/vm-mode.d.ts.map +1 -0
  128. package/dist/runtime/vm-mode.js +62 -0
  129. package/dist/runtime/vm-serde-bundle.generated.d.ts +14 -0
  130. package/dist/runtime/vm-serde-bundle.generated.d.ts.map +1 -0
  131. package/dist/runtime/vm-serde-bundle.generated.js +16 -0
  132. package/dist/runtime/wait-continuation.d.ts +106 -0
  133. package/dist/runtime/wait-continuation.d.ts.map +1 -0
  134. package/dist/runtime/wait-continuation.js +136 -0
  135. package/dist/runtime/wait-until.d.ts +18 -0
  136. package/dist/runtime/wait-until.d.ts.map +1 -0
  137. package/dist/runtime/wait-until.js +42 -0
  138. package/dist/runtime/world-compatibility.d.ts +32 -0
  139. package/dist/runtime/world-compatibility.d.ts.map +1 -0
  140. package/dist/runtime/world-compatibility.js +44 -0
  141. package/dist/runtime/world-init.d.ts +17 -42
  142. package/dist/runtime/world-init.d.ts.map +1 -1
  143. package/dist/runtime/world-init.js +18 -43
  144. package/dist/runtime/world.d.ts +14 -2
  145. package/dist/runtime/world.d.ts.map +1 -1
  146. package/dist/runtime/world.js +75 -19
  147. package/dist/runtime.d.ts +10 -6
  148. package/dist/runtime.d.ts.map +1 -1
  149. package/dist/runtime.js +3741 -490
  150. package/dist/sealed-box.d.ts +167 -0
  151. package/dist/sealed-box.d.ts.map +1 -0
  152. package/dist/sealed-box.js +571 -0
  153. package/dist/serialization/client.d.ts +3 -3
  154. package/dist/serialization/client.d.ts.map +1 -1
  155. package/dist/serialization/client.js +8 -4
  156. package/dist/serialization/codec-devalue-vm.d.ts +16 -0
  157. package/dist/serialization/codec-devalue-vm.d.ts.map +1 -0
  158. package/dist/serialization/codec-devalue-vm.js +148 -0
  159. package/dist/serialization/codec-devalue.d.ts +1 -1
  160. package/dist/serialization/codec-devalue.d.ts.map +1 -1
  161. package/dist/serialization/codec-devalue.js +24 -6
  162. package/dist/serialization/codec.d.ts +37 -2
  163. package/dist/serialization/codec.d.ts.map +1 -1
  164. package/dist/serialization/codec.js +2 -2
  165. package/dist/serialization/compression.d.ts +105 -0
  166. package/dist/serialization/compression.d.ts.map +1 -0
  167. package/dist/serialization/compression.js +261 -0
  168. package/dist/serialization/encryption.d.ts +119 -17
  169. package/dist/serialization/encryption.d.ts.map +1 -1
  170. package/dist/serialization/encryption.js +152 -17
  171. package/dist/serialization/errors.d.ts +13 -0
  172. package/dist/serialization/errors.d.ts.map +1 -1
  173. package/dist/serialization/errors.js +20 -2
  174. package/dist/serialization/format.d.ts +6 -6
  175. package/dist/serialization/format.js +7 -7
  176. package/dist/serialization/hardened.d.ts +156 -0
  177. package/dist/serialization/hardened.d.ts.map +1 -0
  178. package/dist/serialization/hardened.js +538 -0
  179. package/dist/serialization/index.d.ts +10 -8
  180. package/dist/serialization/index.d.ts.map +1 -1
  181. package/dist/serialization/index.js +10 -8
  182. package/dist/serialization/reducers/class-vm.d.ts +20 -0
  183. package/dist/serialization/reducers/class-vm.d.ts.map +1 -0
  184. package/dist/serialization/reducers/class-vm.js +77 -0
  185. package/dist/serialization/reducers/class.d.ts.map +1 -1
  186. package/dist/serialization/reducers/class.js +11 -6
  187. package/dist/serialization/reducers/common-vm.d.ts +15 -0
  188. package/dist/serialization/reducers/common-vm.d.ts.map +1 -0
  189. package/dist/serialization/reducers/common-vm.js +616 -0
  190. package/dist/serialization/reducers/common.d.ts +1 -1
  191. package/dist/serialization/reducers/common.d.ts.map +1 -1
  192. package/dist/serialization/reducers/common.js +202 -62
  193. package/dist/serialization/reducers/step-function-vm.d.ts +44 -0
  194. package/dist/serialization/reducers/step-function-vm.d.ts.map +1 -0
  195. package/dist/serialization/reducers/step-function-vm.js +97 -0
  196. package/dist/serialization/reducers/step-function.d.ts.map +1 -1
  197. package/dist/serialization/reducers/step-function.js +25 -9
  198. package/dist/serialization/step.d.ts +4 -4
  199. package/dist/serialization/step.d.ts.map +1 -1
  200. package/dist/serialization/step.js +9 -5
  201. package/dist/serialization/types.d.ts +80 -1
  202. package/dist/serialization/types.d.ts.map +1 -1
  203. package/dist/serialization/types.js +15 -1
  204. package/dist/serialization/workflow-vm.d.ts +29 -0
  205. package/dist/serialization/workflow-vm.d.ts.map +1 -0
  206. package/dist/serialization/workflow-vm.js +74 -0
  207. package/dist/serialization-format.d.ts +64 -8
  208. package/dist/serialization-format.d.ts.map +1 -1
  209. package/dist/serialization-format.js +225 -73
  210. package/dist/serialization.d.ts +262 -34
  211. package/dist/serialization.d.ts.map +1 -1
  212. package/dist/serialization.js +1879 -274
  213. package/dist/set-attributes.d.ts +13 -0
  214. package/dist/set-attributes.d.ts.map +1 -0
  215. package/dist/set-attributes.js +60 -0
  216. package/dist/source-map.d.ts +25 -0
  217. package/dist/source-map.d.ts.map +1 -1
  218. package/dist/source-map.js +154 -10
  219. package/dist/step/context-storage.d.ts +61 -2
  220. package/dist/step/context-storage.d.ts.map +1 -1
  221. package/dist/step/context-storage.js +7 -5
  222. package/dist/step/writable-stream.d.ts +3 -3
  223. package/dist/step/writable-stream.d.ts.map +1 -1
  224. package/dist/step/writable-stream.js +72 -9
  225. package/dist/step.d.ts.map +1 -1
  226. package/dist/step.js +148 -27
  227. package/dist/symbols.d.ts +50 -0
  228. package/dist/symbols.d.ts.map +1 -1
  229. package/dist/symbols.js +51 -1
  230. package/dist/telemetry/semantic-conventions.d.ts +397 -2
  231. package/dist/telemetry/semantic-conventions.d.ts.map +1 -1
  232. package/dist/telemetry/semantic-conventions.js +297 -3
  233. package/dist/telemetry.d.ts +91 -0
  234. package/dist/telemetry.d.ts.map +1 -1
  235. package/dist/telemetry.js +244 -17
  236. package/dist/test-support/orchestrator-context.d.ts +25 -0
  237. package/dist/test-support/orchestrator-context.d.ts.map +1 -0
  238. package/dist/test-support/orchestrator-context.js +101 -0
  239. package/dist/types.d.ts +6 -0
  240. package/dist/types.d.ts.map +1 -1
  241. package/dist/types.js +23 -1
  242. package/dist/util.d.ts +0 -5
  243. package/dist/util.d.ts.map +1 -1
  244. package/dist/util.js +1 -15
  245. package/dist/version.d.ts +1 -1
  246. package/dist/version.d.ts.map +1 -1
  247. package/dist/version.js +2 -2
  248. package/dist/vm/index.d.ts.map +1 -1
  249. package/dist/vm/index.js +84 -14
  250. package/dist/vm/script-cache.d.ts +26 -0
  251. package/dist/vm/script-cache.d.ts.map +1 -0
  252. package/dist/vm/script-cache.js +131 -0
  253. package/dist/vm/uint8array-base64.js +1 -1
  254. package/dist/workflow/abort-controller.d.ts +2 -2
  255. package/dist/workflow/abort-controller.d.ts.map +1 -1
  256. package/dist/workflow/abort-controller.js +88 -21
  257. package/dist/workflow/attribute-dispatcher.d.ts +6 -0
  258. package/dist/workflow/attribute-dispatcher.d.ts.map +1 -0
  259. package/dist/workflow/attribute-dispatcher.js +45 -0
  260. package/dist/workflow/create-hook.d.ts.map +1 -1
  261. package/dist/workflow/create-hook.js +24 -3
  262. package/dist/workflow/get-workflow-metadata.js +2 -2
  263. package/dist/workflow/hook.d.ts.map +1 -1
  264. package/dist/workflow/hook.js +304 -47
  265. package/dist/workflow/index.d.ts +1 -0
  266. package/dist/workflow/index.d.ts.map +1 -1
  267. package/dist/workflow/index.js +2 -1
  268. package/dist/workflow/set-attributes.d.ts +68 -0
  269. package/dist/workflow/set-attributes.d.ts.map +1 -0
  270. package/dist/workflow/set-attributes.js +60 -0
  271. package/dist/workflow/sleep.d.ts.map +1 -1
  272. package/dist/workflow/sleep.js +58 -12
  273. package/dist/workflow/world-init-stub.d.ts +3 -3
  274. package/dist/workflow/world-init-stub.js +4 -4
  275. package/dist/workflow.d.ts +95 -3
  276. package/dist/workflow.d.ts.map +1 -1
  277. package/dist/workflow.js +895 -561
  278. package/docs/api-reference/create-hook.mdx +89 -10
  279. package/docs/api-reference/create-webhook.mdx +16 -15
  280. package/docs/api-reference/define-hook.mdx +35 -33
  281. package/docs/api-reference/fatal-error.mdx +30 -8
  282. package/docs/api-reference/fetch.mdx +14 -10
  283. package/docs/api-reference/get-step-metadata.mdx +2 -2
  284. package/docs/api-reference/get-workflow-metadata.mdx +3 -3
  285. package/docs/api-reference/get-writable.mdx +7 -7
  286. package/docs/api-reference/index.mdx +4 -1
  287. package/docs/api-reference/retryable-error.mdx +1 -1
  288. package/docs/api-reference/set-attributes.mdx +61 -0
  289. package/docs/api-reference/sleep.mdx +4 -4
  290. package/docs/foundations/cancellation.mdx +31 -32
  291. package/docs/foundations/errors-and-retries.mdx +42 -11
  292. package/docs/foundations/hooks.mdx +64 -35
  293. package/docs/foundations/idempotency.mdx +244 -12
  294. package/docs/foundations/index.mdx +1 -23
  295. package/docs/foundations/meta.json +2 -1
  296. package/docs/foundations/serialization.mdx +21 -22
  297. package/docs/foundations/starting-workflows.mdx +106 -30
  298. package/docs/foundations/streaming.mdx +107 -59
  299. package/docs/foundations/versioning.mdx +263 -0
  300. package/docs/foundations/workflows-and-steps.mdx +9 -9
  301. package/docs/how-it-works/cancellation.mdx +63 -63
  302. package/docs/how-it-works/code-transform.mdx +83 -67
  303. package/docs/how-it-works/encryption.mdx +30 -26
  304. package/docs/how-it-works/event-sourcing.mdx +98 -34
  305. package/docs/how-it-works/framework-integrations.mdx +96 -337
  306. package/docs/how-it-works/understanding-directives.mdx +22 -22
  307. package/package.json +24 -15
  308. package/dist/runtime/step-handler.d.ts +0 -2
  309. package/dist/runtime/step-handler.d.ts.map +0 -1
  310. package/dist/runtime/step-handler.js +0 -776
@@ -0,0 +1,61 @@
1
+ ---
2
+ title: setAttributes
3
+ description: Attach string metadata to workflow run for observability.
4
+ type: reference
5
+ summary: Use setAttributes inside a workflow or step function to set run attributes.
6
+ prerequisites:
7
+ - /docs/foundations/workflows-and-steps
8
+ related:
9
+ - /docs/observability/attributes
10
+ - /docs/api-reference/workflow/fatal-error
11
+ ---
12
+
13
+ Attaches string metadata to the current workflow run.
14
+
15
+ ```typescript lineNumbers
16
+ import { setAttributes } from "workflow"
17
+
18
+ export async function orderWorkflow(orderId: string) {
19
+ "use workflow"
20
+
21
+ await setAttributes({
22
+ phase: "received",
23
+ orderId,
24
+ })
25
+ }
26
+ ```
27
+
28
+ ## API signature
29
+
30
+ ### Parameters
31
+
32
+ <TSDoc
33
+ definition={`
34
+ import { setAttributes } from "workflow";
35
+ export default setAttributes;`}
36
+ showSections={['parameters']}
37
+ />
38
+
39
+ ## Usage
40
+
41
+ Call `setAttributes` from a `"use workflow"` function or a `"use step"` function. Calling it from plain application code is not supported because there is no active workflow run.
42
+
43
+ Attribute values must be strings. Pass `undefined` to remove an attribute:
44
+
45
+ ```typescript lineNumbers
46
+ import { setAttributes } from "workflow"
47
+
48
+ export async function cleanupAttributes() {
49
+ "use workflow"
50
+
51
+ await setAttributes({ staleKey: undefined })
52
+ }
53
+ ```
54
+
55
+ Attribute keys must be 1-256 characters, values must be strings up to 256 bytes, and each run can have up to 64 attributes. Keys that start with `$` are reserved for framework and library code.
56
+
57
+ Validation errors throw [`FatalError`](/docs/api-reference/workflow/fatal-error) and fail the run before an attribute write is attempted.
58
+
59
+ Calls from both workflow and step bodies append a native `attr_set` event, which the World materializes onto `run.attributes`. Workflow-originated events record a workflow writer; step-originated events record the originating step ID and attempt.
60
+
61
+ Native attributes require spec version 4 or later. Step-body storage errors throw from `setAttributes`; catch them inside the step if the write should be best-effort. Workflow-body writes are committed when the workflow suspends: transient storage errors are retried with the suspension, while a write the World rejects as invalid (such as exceeding the per-run attribute cap across multiple calls) fails the run with the validation error.
@@ -14,7 +14,7 @@ Suspends a workflow for a specified duration or until an end date without consum
14
14
  This is useful when you want to resume a workflow after some duration or date.
15
15
 
16
16
  <Callout>
17
- `sleep` is a *special* type of step function and should be called directly inside workflow functions.
17
+ `sleep` is a built-in workflow runtime function (backed by a timer event in the event log, not a step) and should be called directly inside workflow functions.
18
18
  </Callout>
19
19
 
20
20
  ```typescript lineNumbers
@@ -26,7 +26,7 @@ async function testWorkflow() {
26
26
  }
27
27
  ```
28
28
 
29
- ## API Signature
29
+ ## API signature
30
30
 
31
31
  ### Parameters
32
32
 
@@ -39,7 +39,7 @@ showSections={['parameters']}
39
39
 
40
40
  ## Examples
41
41
 
42
- ### Sleeping With a Duration
42
+ ### Sleeping with a duration
43
43
 
44
44
  You can specify a duration for `sleep` to suspend the workflow for a fixed amount of time.
45
45
 
@@ -52,7 +52,7 @@ async function testWorkflow() {
52
52
  }
53
53
  ```
54
54
 
55
- ### Sleeping Until an End Date
55
+ ### Sleeping until an end date
56
56
 
57
57
  You can specify a future `Date` object for `sleep` to suspend the workflow until a specific date.
58
58
 
@@ -6,7 +6,6 @@ summary: Cancel in-flight work with AbortSignal or stop entire workflow runs.
6
6
  prerequisites:
7
7
  - /docs/foundations/workflows-and-steps
8
8
  related:
9
- - /docs/foundations/common-patterns
10
9
  - /docs/foundations/hooks
11
10
  - /docs/how-it-works/cancellation
12
11
  ---
@@ -15,7 +14,7 @@ Workflow DevKit supports two cancellation mechanisms: **AbortSignal** for fine-g
15
14
 
16
15
  ## AbortSignal
17
16
 
18
- `AbortController` and `AbortSignal` work across workflow and step boundaries. Create an `AbortController` with `new AbortController()` in a workflow function, pass its signal to steps, and call `abort()` using the standard [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) API you already know.
17
+ `AbortController` and `AbortSignal` work across workflow and step boundaries. Create an `AbortController` with `new AbortController()` in a workflow function, pass its signal to steps, and call `abort()` using the standard [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) API.
19
18
 
20
19
  ```typescript lineNumbers
21
20
  import { sleep } from "workflow";
@@ -49,17 +48,17 @@ async function longRunningStep(signal: AbortSignal) {
49
48
  }
50
49
  ```
51
50
 
52
- No special imports, no wrapper functions just the standard `AbortController` API.
51
+ Use the standard `AbortController` API without special imports or wrapper functions.
53
52
 
54
53
  <Callout type="info">
55
- Cancellation is **cooperative**. Aborting a signal doesn't forcefully kill a step it's up to the step's code to check `signal.aborted` or pass the signal to APIs like `fetch` that respect it. If a step ignores the signal, it runs to completion.
54
+ Cancellation is **cooperative**. Aborting a signal doesn't forcefully kill a step. The step's code must check `signal.aborted` or pass the signal to APIs like `fetch` that respect it. If a step ignores the signal, it runs to completion.
56
55
  </Callout>
57
56
 
58
57
  <Callout type="info">
59
58
  To learn how `AbortController` works durably across workflow suspensions, replays, and step boundaries, see [How Cancellation Works](/docs/how-it-works/cancellation).
60
59
  </Callout>
61
60
 
62
- ### Timeout with Cancellation
61
+ ### Timeout with cancellation
63
62
 
64
63
  Race a step against a timeout, and cancel the step if the timeout wins:
65
64
 
@@ -91,7 +90,7 @@ async function fetchUrl(url: string, signal: AbortSignal) {
91
90
  }
92
91
  ```
93
92
 
94
- ### Cancelling Parallel Work
93
+ ### Cancelling parallel work
95
94
 
96
95
  When racing multiple steps, cancel the losers:
97
96
 
@@ -117,7 +116,7 @@ async function fetchUrl(url: string, signal: AbortSignal) {
117
116
  }
118
117
  ```
119
118
 
120
- ### Passing Signal Through a Pipeline
119
+ ### Passing signal through a pipeline
121
120
 
122
121
  Pass the same signal to a chain of steps. Aborting cancels whichever step is currently running:
123
122
 
@@ -176,7 +175,7 @@ async function uploadData(data: ArrayBuffer, signal: AbortSignal) {
176
175
  }
177
176
  ```
178
177
 
179
- ### Step-Initiated Abort
178
+ ### Step-initiated abort
180
179
 
181
180
  A step can receive the full `AbortController` and call `abort()` to cancel parallel work. This is useful for watchdog/monitor patterns where one step observes an external condition and cancels other in-flight steps:
182
181
 
@@ -221,7 +220,7 @@ async function monitorQuota(userId: string, controller: AbortController) {
221
220
  }
222
221
  ```
223
222
 
224
- ### User-Triggered Cancellation with Hooks
223
+ ### User-triggered cancellation with hooks
225
224
 
226
225
  Combine hooks with abort controllers to let users cancel in-flight work from an external API:
227
226
 
@@ -267,23 +266,23 @@ export async function POST(request: Request) {
267
266
  }
268
267
  ```
269
268
 
270
- ### How Steps Handle Abort
269
+ ### How steps handle abort
271
270
 
272
271
  When an `AbortSignal` is aborted, the behavior depends on how the step uses it:
273
272
 
274
- | Usage | Behavior on Abort |
273
+ | Usage | Behavior on abort |
275
274
  |-------|-------------------|
276
- | `fetch(url, { signal })` | Request is cancelled, throws `AbortError` |
275
+ | `fetch(url, { signal })` | Request is canceled, throws `AbortError` |
277
276
  | `signal.throwIfAborted()` | Throws the abort reason |
278
277
  | `signal.aborted` check | Returns `true`, step can exit gracefully |
279
278
  | `signal.addEventListener('abort', fn)` | Callback fires, step can clean up |
280
279
  | Ignored | Step runs to completion (abort is cooperative) |
281
280
 
282
- ### Abort Errors Skip Retries
281
+ ### Abort errors skip retries
283
282
 
284
- When a step throws due to an abort (e.g., `fetch` throws `AbortError`, or `signal.throwIfAborted()` throws), the error is automatically wrapped in a `FatalError`. This means the step **skips retries** and the error bubbles up to the workflow immediately.
283
+ When a step throws due to an abort (for example, `fetch` throws `AbortError`, or `signal.throwIfAborted()` throws), the runtime wraps the error in a `FatalError`. The step **skips retries**, and the error immediately bubbles up to the workflow.
285
284
 
286
- This is the correct behavior because an abort is an intentional cancellation retrying the step would just result in another abort. You don't need to manually wrap abort errors in `FatalError`.
285
+ An abort is an intentional cancellation, so retrying the step would result in another abort. You don't need to manually wrap abort errors in `FatalError`.
287
286
 
288
287
  ```typescript lineNumbers
289
288
  import { sleep } from "workflow";
@@ -300,7 +299,7 @@ export async function workflow() {
300
299
  if (result === null) controller.abort();
301
300
  return result;
302
301
  } catch (err) {
303
- // AbortError arrives as FatalError no retries attempted // [!code highlight]
302
+ // AbortError arrives as FatalError, with no retries attempted // [!code highlight]
304
303
  return { status: "cancelled" };
305
304
  }
306
305
  }
@@ -313,7 +312,7 @@ async function cancellableStep(signal: AbortSignal) {
313
312
  }
314
313
  ```
315
314
 
316
- ### Passing AbortSignal as Workflow Input
315
+ ### Passing AbortSignal as workflow input
317
316
 
318
317
  You can pass an `AbortSignal` from external code into a workflow via `start()`:
319
318
 
@@ -330,15 +329,15 @@ export async function POST(request: Request) {
330
329
  }
331
330
  ```
332
331
 
333
- When the signal is serialized at the `start()` boundary, an event listener is attached to the external signal that writes the cancellation packet to the backing stream. This means the external `abort()` propagates into the workflow — but only while the originating process is still alive (same constraint as passing a `ReadableStream` as input).
332
+ When the signal is serialized at the `start()` boundary, an event listener attaches to the external signal and writes the cancellation packet to the backing stream. The external `abort()` propagates into the workflow only while the originating process is still alive (the same constraint as passing a `ReadableStream` as input).
334
333
 
335
334
  <Callout type="info">
336
335
  For reliable external cancellation that works regardless of process lifetime, prefer the [User-Triggered Cancellation with Hooks](#user-triggered-cancellation-with-hooks) pattern. Hooks are durable and don't depend on the caller's process staying alive.
337
336
  </Callout>
338
337
 
339
- ## Run Cancellation
338
+ ## Run cancellation
340
339
 
341
- Run cancellation stops an entire workflow at the next suspension point. Unlike `AbortSignal`, it is not cooperative the workflow does not continue executing after cancellation.
340
+ Run cancellation stops an entire workflow at the next suspension point. Unlike `AbortSignal`, it is not cooperative. The workflow does not continue executing after cancellation.
342
341
 
343
342
  ```typescript title="app/api/cancel-run/route.ts" lineNumbers
344
343
  import { getRun } from "workflow/api";
@@ -354,10 +353,10 @@ export async function POST(request: Request) {
354
353
  ```
355
354
 
356
355
  <Callout type="info">
357
- Calling `run.cancel()` is the same action as clicking the **Cancel** button on a run in the observability UI both produce identical `run_cancelled` events in the event log.
356
+ Calling `run.cancel()` is the same action as clicking the **Cancel** button on a run in the observability UI. Both produce identical `run_cancelled` events in the event log.
358
357
  </Callout>
359
358
 
360
- When a run is cancelled:
359
+ When a run is canceled:
361
360
  - The workflow stops at its next suspension point (step call, hook await, or sleep)
362
361
  - A `run_cancelled` event is recorded in the [event log](/docs/how-it-works/event-sourcing)
363
362
  - All associated hooks are disposed and their tokens released
@@ -367,19 +366,19 @@ When a run is cancelled:
367
366
  Run cancellation does **not** automatically abort any outstanding `AbortSignal`s. Steps that are currently executing will run to completion. If you need in-flight cancellation of specific operations, use `AbortSignal`.
368
367
  </Callout>
369
368
 
370
- ## AbortSignal vs. Run Cancellation
369
+ ## AbortSignal vs. run cancellation
371
370
 
372
371
  | | AbortSignal | Run Cancellation |
373
372
  |---|---|---|
374
373
  | **Scope** | Individual operations within a step | Entire workflow run |
375
374
  | **Triggered by** | Your code (`controller.abort()`) | External API (`run.cancel()`) |
376
- | **Cooperative** | Yes steps must check the signal | No workflow stops at the next suspension point |
375
+ | **Cooperative** | Yes. Steps must check the signal | No. The workflow stops at the next suspension point |
377
376
  | **Granularity** | Can target specific steps or operations | All-or-nothing |
378
377
  | **In-flight steps** | Aborted immediately if using the signal | Run to completion |
379
378
 
380
379
  Use `AbortSignal` when you need fine-grained, in-flight cancellation of specific operations. Use run cancellation when you want to stop the entire workflow.
381
380
 
382
- ## Best Practices
381
+ ## Best practices
383
382
 
384
383
  **Use `throwIfAborted()` before expensive work.** This throws the signal's abort reason if the signal is already aborted, preventing wasted compute:
385
384
 
@@ -449,12 +448,12 @@ export async function workflow() {
449
448
  }
450
449
  ```
451
450
 
452
- This is safe even if both steps have already completed aborting a finished operation is a no-op.
451
+ This is safe even if both steps have already completed because aborting a finished operation is a no-op.
453
452
 
454
- ## Related Documentation
453
+ ## Related documentation
455
454
 
456
- - [How Cancellation Works](/docs/how-it-works/cancellation) Hook and stream backing, serialization internals
457
- - [Serialization](/docs/foundations/serialization) Understanding serializable types
458
- - [Common Patterns](/docs/foundations/common-patterns) Timeout and race patterns
459
- - [Hooks](/docs/foundations/hooks) Pausing workflows for external events
460
- - [Errors and Retries](/docs/foundations/errors-and-retries) Handling step failures
455
+ - [How Cancellation Works](/docs/how-it-works/cancellation): Hook and stream backing, serialization internals
456
+ - [Serialization](/docs/foundations/serialization): Understanding serializable types
457
+ - [Cookbook](/cookbook): Timeout, race, and other reliability patterns
458
+ - [Hooks](/docs/foundations/hooks): Pausing workflows for external events
459
+ - [Errors and Retries](/docs/foundations/errors-and-retries): Handling step failures
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Errors & Retrying
3
- description: Customize retry behavior with FatalError and RetryableError for robust error handling.
3
+ description: Customize retry behavior with FatalError and RetryableError.
4
4
  type: conceptual
5
5
  summary: Control how steps handle failures and customize retry behavior.
6
6
  prerequisites:
@@ -12,7 +12,7 @@ related:
12
12
 
13
13
  By default, errors thrown inside steps are retried. Additionally, Workflow SDK provides two new types of errors you can use to customize retries.
14
14
 
15
- ## Default Retrying
15
+ ## Default retrying
16
16
 
17
17
  By default, steps retry up to 3 times on arbitrary errors. You can customize the number of retries by adding a `maxRetries` property to the step function.
18
18
 
@@ -42,9 +42,9 @@ Steps get enqueued immediately after a failure. Read on to see how this can be c
42
42
  more information.
43
43
  </Callout>
44
44
 
45
- ## Intentional Errors
45
+ ## Intentional errors
46
46
 
47
- When your step needs to intentionally throw an error and skip retrying, simply throw a [`FatalError`](/docs/api-reference/workflow/fatal-error).
47
+ When your step needs to intentionally throw an error and skip retrying, throw a [`FatalError`](/docs/api-reference/workflow/fatal-error).
48
48
 
49
49
  ```typescript lineNumbers
50
50
  import { FatalError } from "workflow";
@@ -67,7 +67,7 @@ async function callApi(endpoint: string) {
67
67
  }
68
68
  ```
69
69
 
70
- ## Customize Retry Behavior
70
+ ## Customize retry behavior
71
71
 
72
72
  When you need to customize the delay on a retry, use [`RetryableError`](/docs/api-reference/workflow/retryable-error) and set the `retryAfter` property.
73
73
 
@@ -97,7 +97,7 @@ async function callApi(endpoint: string) {
97
97
  }
98
98
  ```
99
99
 
100
- ## Advanced Example
100
+ ## Advanced example
101
101
 
102
102
  This final example combines everything we've learned, along with [`getStepMetadata`](/docs/api-reference/workflow/get-step-metadata).
103
103
 
@@ -139,7 +139,32 @@ callApi.maxRetries = 5; // Retry up to 5 times on failure (6 total attempts)
139
139
  step can run up to 4 times total (1 initial attempt + 3 retries).
140
140
  </Callout>
141
141
 
142
- ## Error Codes
142
+ ## Serialization failures
143
+
144
+ A step whose arguments or return value cannot be [serialized](/docs/foundations/serialization) fails like a step whose body threw a `FatalError`: the failure is deterministic, so it skips the retry loop, and a `try/catch` around the step call observes the `SerializationError`:
145
+
146
+ ```typescript lineNumbers
147
+ async function someStep(input: unknown) {
148
+ "use step";
149
+ return input;
150
+ }
151
+
152
+ export async function myWorkflow(input: unknown) {
153
+ "use workflow";
154
+
155
+ try {
156
+ await someStep(input);
157
+ } catch (err) {
158
+ if ((err as Error).name === "SerializationError") {
159
+ // e.g. `Failed to serialize step arguments at path "..."`
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ Uncaught, the run fails immediately with the `USER_ERROR` code, without retrying. See [serialization-failed](/docs/errors/serialization-failed) for common causes and fixes.
166
+
167
+ ## Error codes
143
168
 
144
169
  When a workflow run fails, the error includes an `errorCode` that classifies the failure, alongside the original thrown value (preserved as `cause`):
145
170
 
@@ -153,7 +178,7 @@ try {
153
178
  const result = await run.returnValue;
154
179
  } catch (err) {
155
180
  if (WorkflowRunFailedError.is(err)) {
156
- console.log(err.errorCode); // "USER_ERROR", "RUNTIME_ERROR", or undefined
181
+ console.log(err.errorCode); // e.g. "USER_ERROR", "MAX_EVENTS_EXCEEDED", or undefined
157
182
  // `cause` is the original thrown value, hydrated through the workflow
158
183
  // serialization pipeline. It can be any thrown value, so check shape.
159
184
  if (err.cause instanceof Error) {
@@ -166,13 +191,19 @@ try {
166
191
  | Code | Meaning |
167
192
  | --- | --- |
168
193
  | `USER_ERROR` | An error thrown in your workflow or step code (including propagated step failures like `FatalError`) |
169
- | `RUNTIME_ERROR` | An internal runtime error such as a corrupted event log or missing data. If you see this, please [file an issue](https://github.com/vercel/workflow/issues) |
194
+ | `MAX_EVENTS_EXCEEDED` | The run reached the World's per-run event ceiling (25,000 on the Local and Vercel Worlds). Split unbounded loops into [child workflows](/cookbook/advanced/child-workflows); see [Limits](/docs/configuration/runtime-tuning#limits) |
195
+ | `MAX_DELIVERIES_EXCEEDED` | The run exceeded the maximum number of queue deliveries |
196
+ | `REPLAY_TIMEOUT` | A workflow replay exceeded the maximum allowed duration |
197
+ | `REPLAY_DIVERGENCE` | A replay could not consume the event log deterministically, usually because of non-deterministic workflow code. |
198
+ | `CORRUPTED_EVENT_LOG` | The event log cannot be replayed: it contains orphaned or mismatched events, or one of its stored payloads is no longer readable from the World's storage. If you see this, please [file an issue](https://github.com/vercel/workflow/issues) |
199
+ | `WORLD_CONTRACT_ERROR` | A World response violated the SDK contract; points at a World implementation bug |
200
+ | `RUNTIME_ERROR` | An internal runtime error. If you see this, please [file an issue](https://github.com/vercel/workflow/issues) |
170
201
 
171
202
  <Callout type="info">
172
- The error code is also available on the run entity via the CLI (`npx workflow inspect runs <runId>`) in the `error.code` field, and as an OTEL span attribute (`workflow.error.code`) for observability.
203
+ The error code is also available on the run entity through the Workflow CLI (`npx workflow inspect runs <runId>`) in the `error.code` field, and as an OpenTelemetry span attribute (`workflow.error.code`) for observability.
173
204
  </Callout>
174
205
 
175
- ## Rolling Back Failed Steps
206
+ ## Rolling back failed steps
176
207
 
177
208
  When a workflow fails partway through, it can leave the system in an inconsistent state.
178
209
  A common pattern to address this is "rollbacks": for each successful step, record a corresponding rollback action that can undo it.
@@ -11,9 +11,9 @@ related:
11
11
  - /docs/ai/human-in-the-loop
12
12
  ---
13
13
 
14
- Hooks provide a powerful mechanism for pausing workflow execution and resuming it later with external data. They enable workflows to wait for external events, user interactions (also known as "human in the loop"), or HTTP requests. This guide will teach you the core concepts, starting with the low-level Hook primitive and building up to the higher-level Webhook abstraction.
14
+ Hooks pause workflow execution and resume it later with external data. Workflows can wait for external events, user interactions (also known as "human in the loop"), or HTTP requests.
15
15
 
16
- ## Understanding Hooks
16
+ ## Understanding hooks
17
17
 
18
18
  At their core, **Hooks** are a low-level primitive that allows you to pause a workflow and resume it later with arbitrary [serializable data](/docs/foundations/serialization). Think of them as suspension points in your workflow where you're waiting for external input.
19
19
 
@@ -23,9 +23,9 @@ When you create a hook, it generates a unique token that external systems can us
23
23
  - Receiving data from an external system or service
24
24
  - Implementing event-driven workflows that react to multiple events over time
25
25
 
26
- ### Creating Your First Hook
26
+ ### Creating your first hook
27
27
 
28
- Let's start with a simple example. Here's a workflow that creates a hook and waits for external data:
28
+ This workflow creates a hook and waits for external data:
29
29
 
30
30
  ```typescript lineNumbers
31
31
  import { createHook } from "workflow";
@@ -59,7 +59,7 @@ We recommend using the `using` keyword which implements the [TC39 Explicit Resou
59
59
  See the full API reference for [`createHook()`](/docs/api-reference/workflow/create-hook) for all available options.
60
60
  </Callout>
61
61
 
62
- ### Resuming a Hook
62
+ ### Resuming a hook
63
63
 
64
64
  To send data to a waiting workflow, use [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) from an API route, server action, or any other external context:
65
65
 
@@ -85,9 +85,38 @@ The key points:
85
85
  - You need the hook's `token` to resume it
86
86
  - The workflow will resume execution right where it left off
87
87
 
88
- ### Custom Tokens for Deterministic Hooks
88
+ ### Checking for token conflicts
89
89
 
90
- By default, hooks generate a random token. However, you often want to use a **custom token** that external systems can reconstruct. This is especially useful for long-running workflows where the same workflow instance should handle multiple events.
90
+ Sometimes you need to know that a hook token has been claimed, but you do not want to wait for external data yet. Await `hook.getConflict()` for that:
91
+
92
+ ```typescript lineNumbers
93
+ import { createHook } from "workflow";
94
+
95
+ declare function processOrder(orderId: string): Promise<void>; // @setup
96
+
97
+ export async function orderWorkflow(orderId: string) {
98
+ "use workflow";
99
+
100
+ using hook = createHook({
101
+ token: `order:${orderId}`
102
+ });
103
+
104
+ const conflict = await hook.getConflict(); // [!code highlight]
105
+ if (conflict) { // [!code highlight]
106
+ // Another active run already owns this token.
107
+ return { dedupedTo: conflict.runId };
108
+ }
109
+
110
+ // The hook token is registered and reserved here.
111
+ await processOrder(orderId);
112
+ }
113
+ ```
114
+
115
+ Calling `createHook()` on its own does not register the hook; registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the hook registration, then resolves with `null` once the hook is registered and ready to receive payloads, or with a `Run` handle for the run that owns the token (see [`HookConflictError`](/docs/errors/hook-conflict)). For `hook_conflict` events persisted by older worlds that did not record the owning run's ID, `getConflict()` rejects with `HookConflictError` instead of resolving with an incomplete handle. The conflicting run's accessors are durable steps, so the workflow can inspect `await conflict.status`, wait on `await conflict.returnValue`, or cancel the owner with `await conflict.cancel()`. See [Run idempotency](/docs/foundations/idempotency#run-idempotency) for these strategies.
116
+
117
+ ### Custom tokens for deterministic hooks
118
+
119
+ By default, hooks generate their own token. However, you often want to use a **custom token** that external systems can reconstruct. This is especially useful for long-running workflows where the same workflow instance should handle multiple events.
91
120
 
92
121
  For example, imagine a Slack bot where each channel should have its own workflow instance:
93
122
 
@@ -139,9 +168,9 @@ export async function POST(request: Request) {
139
168
  }
140
169
  ```
141
170
 
142
- ### Receiving Multiple Events
171
+ ### Receiving multiple events
143
172
 
144
- Hooks are _reusable_ - they implement `AsyncIterable`, which means you can use `for await...of` to receive multiple events over time:
173
+ Hooks are _reusable_. They implement `AsyncIterable`, which means you can use `for await...of` to receive multiple events over time:
145
174
 
146
175
  ```typescript lineNumbers
147
176
  import { createHook } from "workflow";
@@ -169,7 +198,7 @@ export async function dataCollectionWorkflow() {
169
198
 
170
199
  Each time you call `resumeHook()` with the same token, the loop receives another value.
171
200
 
172
- ### Disposing Hooks Early
201
+ ### Disposing hooks early
173
202
 
174
203
  When a workflow ends, hooks are automatically disposed. However, you may want to release a hook token early so another workflow can use it while your workflow continues running. Use a block scope with `using` to control when disposal happens:
175
204
 
@@ -211,9 +240,9 @@ hook.dispose(); // Manually release the token
211
240
  After disposal, the hook will no longer receive events and the async iterator will stop yielding values.
212
241
  </Callout>
213
242
 
214
- ## Understanding Webhooks
243
+ ## Understanding webhooks
215
244
 
216
- While hooks are powerful, they require you to manually handle HTTP requests and route them to workflows. **Webhooks** solve this by providing a higher-level abstraction built on top of hooks that:
245
+ Hooks require you to manually handle HTTP requests and route them to workflows. **Webhooks** provide a higher-level abstraction built on top of hooks that:
217
246
 
218
247
  1. Automatically serializes the entire HTTP [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object
219
248
  2. Provides an automatically addressable `url` property pointing to the generated webhook endpoint
@@ -222,16 +251,16 @@ While hooks are powerful, they require you to manually handle HTTP requests and
222
251
  When using Workflow SDK, webhooks are automatically wired up at `/.well-known/workflow/v1/webhook/:token` without any additional setup.
223
252
 
224
253
  <Callout type="warn">
225
- `createWebhook()` exposes a public route at `/.well-known/workflow/v1/webhook/:token`, and the token in that URL is the only authorization performed for incoming requests. This is convenient for prototypes and a simple developer experience because you can share the webhook URL (endpoint) without creating another route, but if you need stronger security, prefer [`createHook()`](/docs/api-reference/workflow/create-hook) behind your own route and authorize the request before calling [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) to avoid unauthenticated workflow resumptions.
254
+ `createWebhook()` exposes a public route at `/.well-known/workflow/v1/webhook/:token`, and the token in that URL is the only authorization performed for incoming requests. This is convenient for prototypes because you can share the webhook URL (endpoint) without creating another route, but if you need stronger security, prefer [`createHook()`](/docs/api-reference/workflow/create-hook) behind your own route and authorize the request before calling [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) to avoid unauthenticated workflow resumptions.
226
255
  </Callout>
227
256
 
228
257
  <Callout type="info">
229
258
  See the full API reference for [`createWebhook()`](/docs/api-reference/workflow/create-webhook) for all available options.
230
259
  </Callout>
231
260
 
232
- ### Creating Your First Webhook
261
+ ### Creating your first webhook
233
262
 
234
- Here's a simple webhook that receives HTTP requests. Like hooks, webhooks support the `using` keyword for automatic cleanup:
263
+ Here's a webhook that receives HTTP requests. Like hooks, webhooks support the `using` keyword for automatic cleanup:
235
264
 
236
265
  ```typescript lineNumbers
237
266
  import { createWebhook } from "workflow";
@@ -255,13 +284,13 @@ export async function webhookWorkflow() {
255
284
  }
256
285
  ```
257
286
 
258
- The webhook will automatically respond with a `202 Accepted` status by default. External systems can simply make an HTTP request to the `webhook.url` to resume your workflow.
287
+ The webhook will automatically respond with a `202 Accepted` status by default. External systems can make an HTTP request to the `webhook.url` to resume your workflow.
259
288
 
260
- ### Sending Custom Responses
289
+ ### Sending custom responses
261
290
 
262
291
  Webhooks provide two ways to send custom HTTP responses: **static responses** and **dynamic responses**.
263
292
 
264
- #### Static Responses
293
+ #### Static responses
265
294
 
266
295
  Use the `respondWith` option to provide a static response that will be sent automatically for every request:
267
296
 
@@ -290,7 +319,7 @@ async function processData(data: any) {
290
319
  }
291
320
  ```
292
321
 
293
- #### Dynamic Responses (Manual Mode)
322
+ #### Dynamic responses (manual mode)
294
323
 
295
324
  For dynamic responses based on the request content, set `respondWith: "manual"` and call the `respondWith()` method on the request:
296
325
 
@@ -336,7 +365,7 @@ export async function webhookWithDynamicResponse() {
336
365
  When using `respondWith: "manual"`, the `respondWith()` method **must** be called from within a step function due to serialization requirements. This requirement may be removed in the future.
337
366
  </Callout>
338
367
 
339
- ### Handling Multiple Webhook Requests
368
+ ### Handling multiple webhook requests
340
369
 
341
370
  Like hooks, webhooks support iteration:
342
371
 
@@ -376,7 +405,7 @@ export async function eventCollectorWorkflow() {
376
405
  }
377
406
  ```
378
407
 
379
- ## Hooks vs. Webhooks: When to Use Each
408
+ ## Hooks vs. webhooks: when to use each
380
409
 
381
410
  | Feature | Hooks | Webhooks |
382
411
  |---------|-------|----------|
@@ -386,19 +415,19 @@ export async function eventCollectorWorkflow() {
386
415
  | **Use Case** | Custom integrations, type-safe payloads | HTTP webhooks, standard REST APIs |
387
416
  | **Resuming** | [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) | Automatic via HTTP, or [`resumeWebhook()`](/docs/api-reference/workflow-api/resume-webhook) |
388
417
 
389
- **Use Hooks when:**
418
+ **Use hooks when:**
390
419
  - You need full control over the payload structure
391
420
  - You're integrating with custom event sources
392
421
  - You want strong TypeScript typing with [`defineHook()`](/docs/api-reference/workflow/define-hook)
393
422
 
394
- **Use Webhooks when:**
423
+ **Use webhooks when:**
395
424
  - You're receiving HTTP requests from external services
396
425
  - You need to send HTTP responses back to the caller
397
426
  - You want automatic URL routing without writing API handlers
398
427
 
399
- ## Advanced Patterns
428
+ ## Advanced patterns
400
429
 
401
- ### Type-Safe Hooks with `defineHook()`
430
+ ### Type-safe hooks with `defineHook()`
402
431
 
403
432
  The [`defineHook()`](/docs/api-reference/workflow/define-hook) helper provides type safety and runtime validation between creating and resuming hooks using [Standard Schema v1](https://standardschema.dev). Use any compliant validator like Zod or Valibot:
404
433
 
@@ -447,25 +476,25 @@ export async function POST(request: Request) {
447
476
 
448
477
  This pattern is especially valuable in larger applications where the workflow and API code are in separate files, providing both compile-time type safety and runtime validation.
449
478
 
450
- ## Best Practices
479
+ ## Best practices
451
480
 
452
- ### Token Design
481
+ ### Token design
453
482
 
454
- Custom tokens are available for `createHook()` with server-side `resumeHook()` only. Webhooks (`createWebhook()`) always use randomly generated tokens to prevent unauthorized access to public webhook endpoints.
483
+ Custom tokens are available for `createHook()` with server-side `resumeHook()` only. Webhooks (`createWebhook()`) always generate their own unique tokens. A generated token is not trivial to guess, but it is not a strong security contract either, so anyone who obtains the URL can invoke an unintended webhook resumption. To prevent unauthenticated run resumptions entirely, prefer a **hook** over the **webhook** convenience and implement your own authentication on the route that calls `resumeHook()`.
455
484
 
456
485
  When using custom tokens with `createHook()`:
457
486
 
458
- - **Make them deterministic**: Base them on data the external system can reconstruct (like channel IDs, user IDs, etc.)
459
- - **Use namespacing**: Prefix tokens to avoid conflicts (e.g., `slack:${channelId}`, `github:${repoId}`)
460
- - **Include routing information**: Ensure the token contains enough information to identify the correct workflow instance
487
+ - **Make them deterministic**: Base them on data the external system can reconstruct, such as channel IDs or user IDs.
488
+ - **Use namespacing**: Prefix tokens to avoid conflicts, such as `slack:${channelId}` or `github:${repoId}`.
489
+ - **Include routing information**: Ensure the token contains enough information to identify the correct workflow instance.
461
490
 
462
- ### Response Handling in Webhooks
491
+ ### Response handling in webhooks
463
492
 
464
- - Use **static responses** (`respondWith: Response`) for simple acknowledgments
493
+ - Use **static responses** (`respondWith: Response`) for acknowledgments
465
494
  - Use **manual mode** (`respondWith: "manual"`) when responses depend on request processing
466
495
  - Remember that `respondWith()` must be called from within a step function
467
496
 
468
- ### Iterating Over Events
497
+ ### Iterating over events
469
498
 
470
499
  Both hooks and webhooks support iteration, making them perfect for long-running event loops:
471
500
 
@@ -484,7 +513,7 @@ for await (const event of hook) {
484
513
 
485
514
  This pattern allows a single workflow instance to handle multiple events over time, maintaining state between events.
486
515
 
487
- ## Related Documentation
516
+ ## Related documentation
488
517
 
489
518
  - [Serialization](/docs/foundations/serialization) - Understanding what data can be passed through hooks
490
519
  - [`createHook()` API Reference](/docs/api-reference/workflow/create-hook)