Skip to content

Commit eed637c

Browse files
docs(automation): every sender-facing text for an inbound api hook teaches the timestamped x-objectstack-signature form only (#22846)
Fixes #22825 Clause-②: no Ruling B on #22805 (maintainer 「同意」, comment 6108492902), step 1: every text that teaches a sender of an inbound `api` hook teaches the timestamped `x-objectstack-signature` form only. The form is the one `signHttpBodyAt` in `packages/core/src/security/http-signature.ts` writes — `t=UNIX_SECONDS,v1=HEX`, where HEX is the HMAC-SHA256 of `UNIX_SECONDS.RAW_BODY` under the flow's secret, and the door refuses a `t` more than `HTTP_SIGNATURE_TOLERANCE_SECONDS` (300) from its clock. The inbound verifier, `packages/core/**`, `packages/triggers/**`, `packages/services/**` and the outbound contract (`content/docs/automation/webhooks.mdx` §6.2, the messaging outbox, the flow `http` node, body-only `signHttpBody`) are not touched. The door's refusal of the body-only form is #22826, which is `Blocked-by:` this card and unblocks on merge. ## The five positions and the changeset | position | before | after | |:--|:--|:--| | `skills/objectstack-automation/SKILL.md`, Inbound webhook section, Signature line (Tier H) | the body-only form, "(GitHub/Stripe style)" | `t=UNIX_SECONDS,v1=HEX`; `v1` = HMAC-SHA256 of `UNIX_SECONDS.RAW_BODY` under `secret`; refused when `t` is over 300 s from the server clock | | `packages/lint/src/rule-explanations.ts`, `flow-api-trigger-secret-missing` explanation (`os explain`) | "carries 'sha256=' and the hex HMAC-SHA256 of the raw body" | the timestamped form, its material, the 300-second window, and why (a captured post stops verifying); `ADR-0041` and `HMAC` kept, which the explanation pin reads | | `packages/lint/src/validate-flow-trigger-readiness.ts`, the rule's `hint` | same sentence | the same form; the verdict `message`, rule id, severity, path and judgement are unchanged | | `examples/app-showcase/src/automation/flows/index.ts`, the `InboundTaskWebhookFlow` doc comment | header line with the body-only form | header line with the timestamped form, `signHttpBodyAt(rawBody, secret, Math.floor(Date.now() / 1000))` named as the writer, the 300 s refusal named | | `docs/qa/platform-checklist/areas/automation.json`, item `automation.trigger-type-matrix`, the `api` step | signs the raw body only | signs with `signHttpBodyAt(rawBody, 'showcase-webhook-secret', Math.floor(Date.now() / 1000))`; `revision` 2 → 3 with a history entry | | `.changeset/22825-inbound-hook-timestamped-guidance.md` | — | `@objectstack/lint` patch + `@objectstack/skills` patch (`@objectstack/example-showcase` is private; `docs/qa` is not a package) | Not changed on purpose: `automation.json` line 429 (another item's step, "a valid x-objectstack-signature HMAC", names no form); `content/docs/releases/**` and `packages/*/CHANGELOG.md` (release-owned); the deployment docs' `#sha256=` artifact pin (a different thing); `skills/objectstack-automation/evals/**` (no eval names the signature: `git grep -i "sha256|signature|x-objectstack" -- skills/objectstack-automation/evals` is empty). ## Done-when readings Grep set `FIVE` = the five files above. Trees named by commit. - **Before** (`origin/main` `d8c7d3864`): `git grep -n "sha256=" -- FIVE` → 5 hits, one per file (`SKILL.md:351`, `rule-explanations.ts:1463`, `validate-flow-trigger-readiness.ts:937`, `flows/index.ts:1581`, `automation.json:557`). - **After** (this branch): the same grep → 0 hits. - **Positive control** (this branch): `git grep -c "v1=" -- FIVE` → `automation.json` 2, `flows/index.ts` 1, `rule-explanations.ts` 1, `validate-flow-trigger-readiness.ts` 1, `SKILL.md` 1; `git grep -c "signHttpBodyAt" -- packages/core/src/security/http-signature.ts` → 6. - **What the wider grep still hits** (`git grep -c "sha256=" -- skills packages/lint examples docs/qa content/docs packages/triggers packages/core`, this branch): `webhooks.mdx` 5 (outbound contract, kept by the ruling), deployment docs 10 (the `#sha256=` artifact pin), `releases/v17/17-6.mdx` 1 (history), `packages/core/CHANGELOG.md` 1 (release-owned), `http-signature.ts` 5 + its test 8 and `api-trigger.ts` 2 + its test 4 (the verifier and its pins — #22826's). None is a sender-facing inbound-hook text. ## `skills/**` readings (token = ceil(utf8 bytes / 4), the ratchet's convention) | surface | before (`d8c7d3864`) | after | |:--|:--|:--| | `skills/objectstack-automation/SKILL.md` | 5783 tokens / 23132 bytes / 438 lines (ceiling 5785, headroom 2) | 5784 tokens / 23135 bytes / 438 lines (ceiling 5785, headroom 1) — `check-skills-token-ratchet` green | | package `skills/objectstack-automation/**/*.md` | 56541 bytes / 1036 lines | 56544 bytes / 1036 lines | | whole catalog, every `skills/**/SKILL.md` | 210520 bytes / 4409 lines | 210523 bytes / 4409 lines | The Signature line grew by 103 bytes and is paid by two same-file deletions of text restated beside it, not by re-wrapping: the `api` row of the Flow Types table loses "every `api` flow is bound to its hook endpoint and" (the Inbound webhook section opens with that sentence), and the Queue-backed bullet loses "Requires the `queue` service (see prerequisite)" (the prerequisites table 25 lines above carries the `queue` row and its 503). The ceiling is not moved. No eval token names the retired spelling, so `evals/**` is untouched. ## Serial state PR #22828 (lint one-line verdicts slice 10, the other writer of `rule-explanations.ts`) landed as `d8c7d3864`, which is this branch's base. `origin/main` was merged at `1eff3224d` before opening; the incoming commits touch `packages/cli`, `packages/verify`, `packages/qa` and `content/docs` only, none of the six files. ## Verification Measured on this worktree; the lock figures are shared-box seconds. The lint suite and typecheck ran on `435fd3b3e` (the content commit); the merge of `origin/main` that followed touched no file under `packages/lint`, `packages/spec`, `packages/formula` or `packages/sdui-parser`, and the derived gate union (74 families, same set as before the merge) was run again on the final head `caf9fa094`, every exit code recorded as the tool prints it, and reconciled: `dispatch-gates --ran: 74 derived famil(ies) accounted for — 72 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3).`; the only non-zero exits are the two NOT MEASURED prerequisites below and `check:platform-checklist`, whose red set on this head is byte-identical to the base's. - `pnpm --filter @objectstack/lint` test (`vitest run --maxWorkers=2`, whole package): `Test Files 137 passed (137)`, `Tests 6450 passed | 5 skipped (6455)`. The two directly affected files first: `rule-explanations.test.ts` + `validate-flow-trigger-readiness.test.ts`, `107 passed (107)` — this is where the explanation pin (`ADR-0041`, `HMAC`, `registerFlow`, ...) lives. - `pnpm --filter @objectstack/lint typecheck`: `tsc --noEmit` clean; `check:test-typecheck: OK — 2 file(s) / 6 error(s) / 2 pinned signature(s) held` (the ledger as committed). - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 74 families from the six-path change set; each was run in the foreground with its exit code recorded as the tool prints it; first-pass `--ran` verdict on `435fd3b3e`: `74 derived famil(ies) accounted for — 68 run, 6 NOT-MEASURED (6 DERIVED from a recorded exit 3)`, 0 UNRUN; the final pass on `caf9fa094` is quoted above (72 run, 2 NOT-MEASURED, 0 UNRUN). Green among them: `check-skills-token-ratchet` (+ `--self-test`), `check:skill-docs`, `check:skill-compatibility`, `check:skill-frame-sync`, `check:skill-identifier-liveness`, `check:doc-authoring`, `check:nul-bytes`, `check:pm-governed-merges`, `check-empty-changeset`, `check-changeset-no-major`, `check-adr-0087-registration`, `check:issue-citations`, `check:cross-package-test-inputs`, `check:test-source-alias`, and the rest of the list. - Dispatch-named gates outside the derived list: `pnpm --filter @objectstack/spec run check:skill-refs` → `9 generated files in sync`; `check:skill-examples` → `exit 0` once `@objectstack/client-react` (its client-SDK root) was built; its first run was a prerequisite refusal (`exit 3`, no built output to read), not a finding. - NOT MEASURED locally, declared to CI: `pnpm check:i18n-coverage` (prerequisite: the built CLI; this diff touches no label, translation or metadata form) and `pnpm check:dual-build-cjs-loads` (prerequisite: all 81 package dists; this diff changes string literals in `@objectstack/lint` only). The other four `exit 3` refusals of the first pass were re-run green once their prerequisite was met: `check:docs-transcript-drift` (`4 declared transcript value(s) across 412 page(s) … equal what the registry derives`) and `check:doc-formula-expressions` after `@objectstack/lint` was built, `check:lean-entry-closure` (`2 published condition(s) measured from a real load`) after `@objectstack/objectql` was built, and `check-plugin-teardown-shape --self-test` (`48 cases pass`) after `git fetch --deepen=4000` brought its fixture commit `621a4876…` into reach. - `pnpm check:platform-checklist` exits 1 on this branch AND on the untouched base `d8c7d3864` with a byte-identical set of 5 unique findings (absent symbol anchors in `areas/access-security.json` and `areas/attachments-storage.json`); none names `automation.json`, which parses (`json.load`) and carries the bumped `revision` 3 with its history entry. `checklist-status.yml` keeps that gate out of per-PR CI; it is a watchdog finding on `main`, not this PR's. - `@objectstack/example-showcase`: the diff is inside one `/** … */` block (comment only), so its typecheck was not run. - `check:commit-card-trailers` (pre-push) is green on every pushed commit: the trailer pair is model-free and no commit carries a card relation; this body is the one carrier. ## Acceptance notes - `docs/qa/platform-checklist/areas/automation.json` line 429 (item `automation.flow-node-type-matrix`'s webhook chain step) says "a valid x-objectstack-signature HMAC (secret 'showcase-webhook-secret')" and names no form. It is not one of the five and is left as is; once #22826 lands, "valid" means the timestamped form by construction, and the showcase comment this step points at now teaches it. - The `check:platform-checklist` reds above are pre-existing on `main` (symbol anchors pointing at `envWideRawViewRows`, `anonymousFormIntakeOrgScopeRefusal`, `anonymousFormIntakeReopenRefusal` in `packages/metadata-protocol/src/protocol.ts` and `canEdit` in `packages/services/service-storage/src/attachment-access-hooks.ts`, which those files no longer declare). Carrier: the checklist watchdog / whoever renamed those symbols; noted here, not filed. - The ratchet's token convention under-prices nothing here: the edit is ASCII apart from one em dash and the arrows already in the file. ## 维护者速读(草稿) **改了什么。** 五处「教发送方怎么给入站 webhook 签名」的文本,从只签请求体的旧写法改为带时间戳的 `t=UNIX_SECONDS,v1=HEX` 写法(`v1` 是对 `UNIX_SECONDS.RAW_BODY` 用流程 `secret` 做的 HMAC-SHA256,服务端只接受与自身时钟相差 300 秒以内的 `t`)。五处是:对外发布的 `objectstack-automation` 技能的 Inbound webhook 一节(Tier H,需要您的 APPROVED)、lint 规则 `flow-api-trigger-secret-missing` 的提示与 `os explain` 解释、showcase 示例的注释、平台测试清单里 `api` 触发的请求配方;另加一个 changeset(`@objectstack/lint` 与 `@objectstack/skills` 各 patch)。校验器、出站签名、`webhooks.mdx` §6.2 一字未动。 **为什么改。** #22805 裁决 B(您批「同意」)第 1 步:先让所有教材只教带时间戳的形式,再让入站门拒收旧形式(#22826,被本卡阻塞)。旧形式不含时间,抓到一次请求可以永远重放;新形式是门已经在接受、`@objectstack/core` 的 `signHttpBodyAt` 已经在写的形式,教材落后于代码。 **风险与代价(含回滚)。** 纯文本改动,无行为变化:lint 的判定、规则 id、严重级别不变,整包 137 个测试文件 6450 用例全绿,typecheck 全绿。对外技能文件的 token 棘轮只有 2 的余量,新签名行多出 102 字节,用同文件两处与相邻表格重复的半句话抵掉(未折行、未抬上限),落地后余量 1。代价是下一位编辑该文件的人仍要先删再加。回滚即 revert 本 PR 的单个 squash 提交,无数据、无迁移。 **席位意见。** (留空,席位定稿成评论。) **你要做的。** 本 PR 含 `skills/**`,属 Tier H:只需您在 PR 上给一个 APPROVED review(任一提交上、不撤回即可),席位随后负责排队落地;不需要您合并。若您认为技能里那一行的措辞应改(例如要不要保留「Stripe style」的类比),在 review 里写一句即可,席位代改。 --- _Generated by [Claude Code](https://claude.ai/code/session_011u73oxZ5X95qrARPTeoU6v)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent ea9fd99 commit eed637c

6 files changed

Lines changed: 43 additions & 16 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/lint': patch
3+
'@objectstack/skills': patch
4+
---
5+
6+
docs(automation): every text that teaches a sender of an inbound `api` hook teaches the timestamped `x-objectstack-signature` form only — `t=<unix seconds>,v1=<hex>`, the HMAC-SHA256 of `<t>.<raw body>` under the flow's secret, refused when `t` is more than 300 seconds from the server clock
7+
8+
Clause-②: no
9+
10+
The inbound hook door has accepted the timestamped form since `signHttpBodyAt` landed in `@objectstack/core`, and it is the only form that stops a captured post from verifying forever; the published teaching still showed the body-only `sha256=<hex>` form. The guidance converges first; the door's refusal of the body-only form is a later change of its own.
11+
12+
- **`@objectstack/skills`.** The `objectstack-automation` skill's Inbound webhook section teaches `t=<unix seconds>,v1=<hex>`: `v1` is the HMAC-SHA256 of `<t>.<raw body>` under the start node's `secret`, and the door refuses a `t` more than 300 seconds from its clock. The body-only form is no longer shown.
13+
- **`@objectstack/lint`.** The hint of `flow-api-trigger-secret-missing` and its `os explain` entry describe the same form. The rule's id, severity, verdict sentence and judgement are unchanged.
14+
- The showcase example's comment and the platform checklist's intake recipe sign the same way.
15+
16+
**Nothing to migrate for an author.** A sender that already signs with `signHttpBodyAt` is unchanged. **The outbound contract stays as published:** the messaging outbox and the flow `http` node keep signing body-only (`signHttpBody`) for third-party receivers, and the inbound door's accept set is not changed by this release.

‎docs/qa/platform-checklist/areas/automation.json‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@
530530
"title": "Every flow trigger type fires and produces a run of its bound flow; the run's trigger block records the launch event",
531531
"since": "v15",
532532
"status": "active",
533-
"revision": 2,
533+
"revision": 3,
534534
"priority": "P1",
535535
"surface": "mixed",
536536
"personas": [
@@ -554,7 +554,7 @@
554554
"boot showcase isolated (dogfood §0); sign in as the dev admin",
555555
"record_change (create leg): POST /api/v1/data/showcase_task with priority='urgent' — showcase_urgent_task_alert fires via record-after-write with previous == null (#3427)",
556556
"record_change (update leg): POST a normal-priority task, then PATCH priority→'urgent'; also PATCH a task status→'done' and confirm a further save WITHOUT a transition fires nothing",
557-
"api: POST /api/v1/automation/hooks/showcase_inbound_task_webhook/intake with JSON {title, assignee, project} and header x-objectstack-signature: sha256=<HMAC-SHA256 of the raw body, key 'showcase-webhook-secret'> — expect a 202 ACK; then find the created showcase_task",
557+
"api: POST /api/v1/automation/hooks/showcase_inbound_task_webhook/intake with JSON {title, assignee, project} and header x-objectstack-signature: t=<unix seconds>,v1=<HMAC-SHA256 of '<t>.<raw body>', key 'showcase-webhook-secret'> — signHttpBodyAt(rawBody, 'showcase-webhook-secret', Math.floor(Date.now() / 1000)) from @objectstack/core writes it, and the door refuses a t more than 300 s from the server clock — expect a 202 ACK; then find the created showcase_task",
558558
"schedule: wait up to ~60s for showcase_scheduled_digest (interval 60000ms) to tick; wait a second interval for a second tick",
559559
"screen: from /apps/showcase_app/showcase_task run the row action showcase_bulk_reassign, fill the dialog, submit",
560560
"autolaunched: POST /api/v1/automation/showcase_batch_reminders/trigger {params:{tasks:[…]}}",
@@ -630,6 +630,12 @@
630630
"date": "2026-10-05",
631631
"change": "clause 1, step 9 and the title re-pointed (assertion defect, #21784 VF4). The service-automation engine's buildRunTrigger sets trigger.type = context.event ?? 'manual': the launch event, not Flow.type, and execution.zod.ts describes trigger.type as a free string. This predates 17.6.0 (#7533). The verifier's wording is used: the kind is identified through the run's flow, and trigger.type is recorded as the launch event",
632632
"ref": "#21797"
633+
},
634+
{
635+
"revision": 3,
636+
"date": "2026-10-11",
637+
"change": "api step: the intake recipe signs with the timestamped x-objectstack-signature form t=<unix seconds>,v1=<hex> that signHttpBodyAt in @objectstack/core writes (HMAC-SHA256 of '<t>.<raw body>' under the secret; the door refuses a t more than 300 s from its clock), no longer with the body-only form. Guidance step of the inbound-hook convergence; the door's refusal of the body-only form is the follow-up card",
638+
"ref": "#22825"
633639
}
634640
]
635641
},

‎examples/app-showcase/src/automation/flows/index.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,13 +1578,15 @@ export const ReleaseSignoffFlow = defineFlow({
15781578
* or a schedule. The start node's config arms the hook:
15791579
*
15801580
* POST /api/v1/automation/hooks/showcase_inbound_task_webhook/intake
1581-
* x-objectstack-signature: sha256=<hmac-sha256 of the raw body, key below>
1581+
* x-objectstack-signature: t=<unix seconds>,v1=<hmac-sha256 of "<t>.<raw body>", key below>
15821582
* { "title": "...", "assignee": "...", "project": "<showcase_project id>" }
15831583
*
1584-
* The trigger validates the HMAC (constant-time), enqueues, and ACKs 202; a
1585-
* queue consumer runs the flow with the JSON payload as the trigger record —
1586-
* so `{record.title}` here reads straight from the webhook body, exactly like
1587-
* a record-change flow reads its record.
1584+
* `signHttpBodyAt(rawBody, secret, Math.floor(Date.now() / 1000))` from
1585+
* `@objectstack/core` writes that header value. The trigger validates the HMAC
1586+
* (constant-time) and refuses a `t` more than 300 s from its own clock, then
1587+
* enqueues and ACKs 202; a queue consumer runs the flow with the JSON payload
1588+
* as the trigger record — so `{record.title}` here reads straight from the
1589+
* webhook body, exactly like a record-change flow reads its record.
15881590
*/
15891591
export const InboundTaskWebhookFlow = defineFlow({
15901592
name: 'showcase_inbound_task_webhook',

‎packages/lint/src/rule-explanations.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,9 +1460,11 @@ const FLOW_API_TRIGGER_SECRET_MISSING_EXPLANATION: RuleExplanation = {
14601460
covers: 'why an inbound api flow needs a secret',
14611461
paragraphs: [
14621462
'An inbound api hook is armed only with a per-flow secret that every post is HMAC-verified against ' +
1463-
'(ADR-0041): the `x-objectstack-signature` header carries \'sha256=\' and the hex HMAC-SHA256 of ' +
1464-
'the raw body. A usable secret is a string that is not blank after trimming, on the start node\'s ' +
1465-
'`config.secret`; absent, blank and non-string are one verdict.',
1463+
'(ADR-0041): the `x-objectstack-signature` header carries `t=<unix seconds>,v1=<hex>`, where the hex ' +
1464+
'is the HMAC-SHA256 of `<t>.<raw body>` under the secret, and the door refuses a `t` more than 300 ' +
1465+
'seconds from its own clock, so a captured post stops verifying. A usable secret is a string that is ' +
1466+
'not blank after trimming, on the start node\'s `config.secret`; absent, blank and non-string are one ' +
1467+
'verdict.',
14661468
'The automation engine refuses to register a flow bound to the api trigger without one — a ' +
14671469
'hardcoded check in `registerFlow`, before any trigger is consulted and whatever the flow\'s ' +
14681470
'`status` — and the trigger refuses to arm it, so the flow never receives a post. The `/automation` ' +

‎packages/lint/src/validate-flow-trigger-readiness.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -934,9 +934,10 @@ export function validateFlowApiTriggerSecret(
934934
`refuses to register the flow`,
935935
hint:
936936
`Set a non-blank string config.secret on the start node, and sign each post with it: the ` +
937-
`x-objectstack-signature header carries 'sha256=' and the hex HMAC-SHA256 of the raw body. A flow that is ` +
938-
`only ever started explicitly and never receives inbound posts is type: 'autolaunched', with no ` +
939-
`triggerType: 'api' on its start node — that flow needs no secret.`,
937+
`x-objectstack-signature header carries t=<unix seconds>,v1=<hex>, the hex being the HMAC-SHA256 of ` +
938+
`'<t>.<raw body>' under the secret, and a t more than 300 seconds from the server clock is refused. ` +
939+
`A flow that is only ever started explicitly and never receives inbound posts is type: 'autolaunched', ` +
940+
`with no triggerType: 'api' on its start node — that flow needs no secret.`,
940941
});
941942
});
942943
return findings;

‎skills/objectstack-automation/SKILL.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ A **Flow** is a directed graph of nodes — the primary automation building bloc
4848
| `screen` | Interactive — presents UI screens to the user (wizards, forms) |
4949
| `schedule` | Runs on a cron/interval cadence declared on the **start node's `config.schedule`** (daily cleanup, weekly reports) — or a **per-record date sweep** via `config.timeRelative`, see *Time-relative triggers* |
5050
| `record_change` | Fires automatically on record create/update/delete (bind via the `start` node's `triggerType`). `autolaunched` + the same `record-*` binding behaves identically — the engine reads the start node either way; `record_change` also opts into the trigger-readiness lint |
51-
| `api` | Inbound **webhook** — every `api` flow is bound to its hook endpoint and needs a start-node `secret` (see *Inbound webhook triggers* below); a flow only ever started explicitly is `autolaunched` |
51+
| `api` | Inbound **webhook** — needs a start-node `secret` (see *Inbound webhook triggers* below); a flow only ever started explicitly is `autolaunched` |
5252

5353
### Flow Node Types
5454

@@ -348,9 +348,9 @@ node `config`** (the start `config` is a free-form record with no Zod shape):
348348
| `hookId` | URL path token (default `'default'`). **Rotate it to revoke** a leaked endpoint |
349349
| `secret` | HMAC-SHA256 shared secret. **Required** — without a non-blank one the flow is refused at registration (`os validate` too) and never armed at boot; the signature goes in `x-objectstack-signature` |
350350

351-
- **Signature:** `sha256=<hex>` (GitHub/Stripe style).
351+
- **Signature:** `t=<unix seconds>,v1=<hex>` — `v1` = HMAC-SHA256 of `<t>.<raw body>` under `secret`; refused when `t` is over 300 s from the server clock.
352352
- **Idempotency:** `x-idempotency-key` dedupes retries — author the flow to be idempotent (delivery is at-least-once).
353-
- **Queue-backed:** the endpoint ACKs `202` and enqueues; the flow runs on the consumer, never in-band. Requires the `queue` service (see prerequisite).
353+
- **Queue-backed:** the endpoint ACKs `202` and enqueues; the flow runs on the consumer, never in-band.
354354
- The JSON body surfaces to the flow as the trigger record (`record.*` / bare fields) plus `params`.
355355

356356
### Trigger Types (start-node `config.triggerType`)

0 commit comments

Comments
 (0)